import { z } from "zod";
const flagValueSchema = z.union([
z.boolean(),
z.object({
rollout: z.number().min(0).max(100),
variant: z.string().optional(),
}),
]);
export const flagsSchema = z.object({
newCheckout: flagValueSchema,
darkMode: flagValueSchema,
betaSearch: flagValueSchema,
});
export type FlagConfig = z.infer<typeof flagsSchema>;
export type FlagKey = keyof FlagConfig;
export const defaultFlags: FlagConfig = {
newCheckout: false,
darkMode: true,
betaSearch: { rollout: 10 },
};
export function parseFlags(raw: unknown): FlagConfig {
return flagsSchema.parse(raw);
}
import React, { createContext, useEffect, useState } from "react";
import { FlagConfig, defaultFlags, parseFlags } from "./flags";
export interface FlagContextValue {
flags: FlagConfig;
stableBucket: number;
}
export const FlagContext = createContext<FlagContextValue | null>(null);
function hashToBucket(userId: string): number {
let h = 0;
for (let i = 0; i < userId.length; i++) {
h = (h * 31 + userId.charCodeAt(i)) | 0;
}
return Math.abs(h) % 100;
}
export function FlagProvider(props: {
userId: string;
children: React.ReactNode;
}) {
const [flags, setFlags] = useState<FlagConfig>(defaultFlags);
useEffect(() => {
let active = true;
fetch("/api/flags")
.then((res) => res.json())
.then((raw) => {
if (active) setFlags(parseFlags(raw));
})
.catch(() => {
if (active) setFlags(defaultFlags);
});
return () => {
active = false;
};
}, []);
const value: FlagContextValue = {
flags,
stableBucket: hashToBucket(props.userId),
};
return (
<FlagContext.Provider value={value}>{props.children}</FlagContext.Provider>
);
}
import React, { useContext } from "react";
import { FlagContext } from "./FlagProvider";
import { FlagKey } from "./flags";
export interface FlagResult {
enabled: boolean;
variant?: string;
}
export function useFeatureFlag(key: FlagKey): FlagResult {
const ctx = useContext(FlagContext);
if (!ctx) {
throw new Error("useFeatureFlag must be used within a FlagProvider");
}
const value = ctx.flags[key];
if (typeof value === "boolean") {
return { enabled: value };
}
const included = ctx.stableBucket < value.rollout;
return { enabled: included, variant: included ? value.variant : undefined };
}
export function FeatureGate(props: {
flag: FlagKey;
children: React.ReactNode;
fallback?: React.ReactNode;
}) {
const { enabled } = useFeatureFlag(props.flag);
return <>{enabled ? props.children : props.fallback ?? null}</>;
}
This snippet shows a small but complete feature-flag system for a React app: a validated config loader, a context provider that exposes the parsed flags, and a typed useFeatureFlag hook plus a <FeatureGate> component that conditionally renders UI. The goal is to make flag usage type-safe end to end, so a typo in a flag name is a compile error rather than a silent undefined at runtime.
In flags.ts, the set of known flags is defined once with a Zod schema. Each flag can be a plain boolean or an object carrying a rollout percentage and an optional variant, which covers both simple kill-switches and gradual rollouts. The schema is the single source of truth: FlagKey and FlagConfig are derived from it with z.infer, so the type system and the runtime validator never drift apart. parseFlags runs the raw payload through flagsSchema.parse, which throws on malformed data — a deliberate choice, because booting with a broken flag config is worse than failing fast.
FlagProvider.tsx wires the parsed config into React. It fetches the raw JSON, validates it with parseFlags, and stores the result in state, falling back to defaultFlags if the network call fails so the app still renders. The parsed flags plus a stableBucket (a deterministic hash of the user id) are placed on a context. Bucketing on a stable hash rather than Math.random() matters: a user must consistently land on the same side of a rollout across reloads, otherwise the UI would flicker between variants.
useFeatureFlag.ts is where the typing pays off. The hook accepts a key: FlagKey, so only real flag names are allowed. It normalizes both flag shapes into a single { enabled, variant } result and compares the user's stableBucket against the rollout threshold to decide inclusion. The companion FeatureGate component reads the same hook and renders children or an optional fallback, keeping conditional-rendering logic out of feature components.
The trade-off is that all flags are known at build time, which suits a typed client but not fully dynamic experimentation. For most product work that constraint is a feature, not a limitation: it turns flag mistakes into red squiggles instead of production incidents.
Related snips
class CommentsController < ApplicationController
before_action :set_post
def create
@comment = @post.comments.build(comment_params)
System test: asserting Turbo Stream responses
import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
static targets = ["form"]
static values = { delay: { type: Number, default: 250 } }
Debounced live search with Stimulus + Turbo Streams
import axios, { AxiosError } from 'axios'
import { v4 as uuidv4 } from 'uuid'
const api = axios.create({
baseURL: import.meta.env.VITE_API_URL || 'http://localhost:3000/api/v1',
timeout: 15000,
Axios API client with interceptors
import { create } from 'zustand'
import { persist } from 'zustand/middleware'
interface FilterState {
search: string
category: string | null
Zustand for lightweight state management
use my_crate::add;
#[test]
fn test_public_api() {
assert_eq!(add(3, 4), 7);
}
Integration tests in tests/ directory
class AddSettingsToAccounts < ActiveRecord::Migration[7.1]
disable_ddl_transaction!
def change
add_column :accounts, :settings, :jsonb, null: false, default: {}
Postgres JSONB Partial Index for Feature Flags
Share this code
Here's the card — post it anywhere.