
React Syntax Context
- 11 installs
- 6 repo stars
- Updated July 8, 2026
- openaec-foundation/react-claude-skill-package
Helps with frontend development tasks.
About
react-syntax-context is a Claude Code skill for frontend development. It helps solo builders move faster with AI-assisted development.
- react-syntax-context
- Frontend Development
- AI-coding skill
React Syntax Context by the numbers
- 11 all-time installs (skills.sh)
- Ranked #1,658 of 2,245 Frontend Development skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/openaec-foundation/react-claude-skill-package --skill react-syntax-contextAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 11 |
|---|---|
| repo stars | ★ 6 |
| Last updated | July 8, 2026 |
| Repository | openaec-foundation/react-claude-skill-package ↗ |
What it does
Helps with frontend development tasks.
Files
react-syntax-context
Quick Reference
Context API Surface
| API | Purpose | Version |
|---|---|---|
createContext<T>(defaultValue) | Create a context object with TypeScript generic | React 18+ |
useContext(Context) | Consume the nearest Provider value | React 18+ |
use(Context) | Consume context inside conditionals/loops | React 19 only |
<Context.Provider value={...}> | Provide value to descendants | React 18 |
<Context value={...}> | Provide value to descendants (no .Provider) | React 19 |
Critical Warnings
NEVER create a new object literal directly in the Provider value prop without useMemo -- this creates a new reference every render and forces ALL consumers to re-render.
NEVER use Context for frequently changing values (e.g., mouse position, scroll offset, animation frames) -- use useSyncExternalStore or an external state library instead.
NEVER put everything in a single global context -- split by concern (theme, auth, locale) to prevent unrelated re-renders.
ALWAYS provide a custom hook wrapper (e.g., useAuth()) around useContext -- this centralizes the missing-provider check and improves API ergonomics.
ALWAYS use useMemo to stabilize context value objects -- this prevents unnecessary consumer re-renders when the provider's parent re-renders.
---
Decision Tree: Do You Need Context?
Need to share state across components?
├── Only 1-2 levels deep? → Pass props directly (no Context needed)
├── Many levels deep but rarely changes? → Use Context
├── Changes frequently (>1x per second)? → Use external store (Zustand, Jotai, useSyncExternalStore)
├── Server-only data (no interactivity)? → Pass as props from Server Component
└── Complex state with actions? → Context + useReducer---
Creating Context with TypeScript
Pattern: Null Default with Type Assertion
import { createContext, useContext, type ReactNode } from "react";
// 1. Define the context type
interface AuthContextType {
user: User | null;
login: (credentials: Credentials) => Promise<void>;
logout: () => void;
}
// 2. Create with null default -- ALWAYS use this pattern for contexts
// that REQUIRE a provider
const AuthContext = createContext<AuthContextType | null>(null);
// 3. Custom hook with missing-provider guard
function useAuth(): AuthContextType {
const context = useContext(AuthContext);
if (context === null) {
throw new Error("useAuth must be used within an AuthProvider");
}
return context;
}Why null default? Passing a "real" default value hides bugs where a Provider is missing. The null pattern forces an explicit error at the call site.
Pattern: Safe Default (No Provider Required)
// Use when a sensible default exists and Provider is optional
const ThemeContext = createContext<"light" | "dark">("light");
// No null check needed -- always returns a valid value
function useTheme(): "light" | "dark" {
return useContext(ThemeContext);
}---
Provider Patterns
Custom Provider Component
ALWAYS encapsulate state logic inside a custom Provider component:
interface AuthProviderProps {
children: ReactNode;
}
function AuthProvider({ children }: AuthProviderProps): JSX.Element {
const [user, setUser] = useState<User | null>(null);
const login = useCallback(async (credentials: Credentials) => {
const result = await authApi.login(credentials);
setUser(result.user);
}, []);
const logout = useCallback(() => {
setUser(null);
authApi.logout();
}, []);
// ALWAYS stabilize the value object with useMemo
const value = useMemo<AuthContextType>(
() => ({ user, login, logout }),
[user, login, logout]
);
// React 19: <AuthContext value={value}>
// React 18: <AuthContext.Provider value={value}>
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
}Nested Providers
The closest Provider wins. Inner providers override outer ones:
function App(): JSX.Element {
return (
<ThemeContext.Provider value="dark">
<Sidebar />
<ThemeContext.Provider value="light">
<MainContent /> {/* reads "light" */}
</ThemeContext.Provider>
</ThemeContext.Provider>
);
}---
Multiple Contexts: Split by Concern
ALWAYS split unrelated concerns into separate contexts:
// GOOD -- independent contexts
const ThemeContext = createContext<ThemeContextType | null>(null);
const AuthContext = createContext<AuthContextType | null>(null);
const LocaleContext = createContext<LocaleContextType | null>(null);
// Compose providers at app root
function AppProviders({ children }: { children: ReactNode }): JSX.Element {
return (
<AuthProvider>
<ThemeProvider>
<LocaleProvider>
{children}
</LocaleProvider>
</ThemeProvider>
</AuthProvider>
);
}Why split? When user changes in a combined context, components that only need theme still re-render. Separate contexts prevent this.
---
Performance: Splitting Read and Write Contexts
For state + dispatch patterns, split into TWO contexts to prevent dispatch-only consumers from re-rendering when state changes:
const TodosStateContext = createContext<TodosState | null>(null);
const TodosDispatchContext = createContext<TodosDispatch | null>(null);
function TodosProvider({ children }: { children: ReactNode }): JSX.Element {
const [state, dispatch] = useReducer(todosReducer, initialState);
// State changes every update -- only state consumers re-render
// Dispatch is stable -- dispatch consumers NEVER re-render from state changes
return (
<TodosStateContext.Provider value={state}>
<TodosDispatchContext.Provider value={dispatch}>
{children}
</TodosDispatchContext.Provider>
</TodosStateContext.Provider>
);
}
// Targeted hooks
function useTodosState(): TodosState {
const ctx = useContext(TodosStateContext);
if (ctx === null) throw new Error("useTodosState requires TodosProvider");
return ctx;
}
function useTodosDispatch(): TodosDispatch {
const ctx = useContext(TodosDispatchContext);
if (ctx === null) throw new Error("useTodosDispatch requires TodosProvider");
return ctx;
}Components that only call useTodosDispatch() will NOT re-render when todos state changes.
---
Context + useReducer
ALWAYS prefer useReducer over useState when context manages complex state with multiple actions:
type TodoAction =
| { type: "ADD"; text: string }
| { type: "TOGGLE"; id: number }
| { type: "DELETE"; id: number };
interface TodosState {
todos: Todo[];
filter: "all" | "active" | "completed";
}
type TodosDispatch = React.Dispatch<TodoAction>;
function todosReducer(state: TodosState, action: TodoAction): TodosState {
switch (action.type) {
case "ADD":
return {
...state,
todos: [...state.todos, { id: Date.now(), text: action.text, done: false }],
};
case "TOGGLE":
return {
...state,
todos: state.todos.map((t) =>
t.id === action.id ? { ...t, done: !t.done } : t
),
};
case "DELETE":
return {
...state,
todos: state.todos.filter((t) => t.id !== action.id),
};
}
}---
Value Stabilization with useMemo
// BAD -- new object every render, ALL consumers re-render
function ThemeProvider({ children }: { children: ReactNode }): JSX.Element {
const [theme, setTheme] = useState<"light" | "dark">("light");
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
{children}
</ThemeContext.Provider>
);
}
// GOOD -- stable reference, consumers re-render only when theme changes
function ThemeProvider({ children }: { children: ReactNode }): JSX.Element {
const [theme, setTheme] = useState<"light" | "dark">("light");
const value = useMemo(() => ({ theme, setTheme }), [theme]);
return (
<ThemeContext.Provider value={value}>
{children}
</ThemeContext.Provider>
);
}---
React 19: use(Context) and Context as Provider
use(Context) -- Conditional Context Reading
// React 19 ONLY -- use() can be called inside conditionals
function StatusBadge({ showAuth }: { showAuth: boolean }): JSX.Element {
if (showAuth) {
// ALLOWED with use() -- FORBIDDEN with useContext()
const { user } = use(AuthContext);
return <Badge user={user} />;
}
return <GuestBadge />;
}Context as Provider (No .Provider)
// React 19 -- render Context directly
<ThemeContext value={theme}>
<App />
</ThemeContext>
// React 18 -- must use .Provider
<ThemeContext.Provider value={theme}>
<App />
</ThemeContext.Provider>React 19 will deprecate <Context.Provider> in a future minor release.
---
When NOT to Use Context
| Scenario | Why Not Context | Use Instead |
|---|---|---|
| Frequently changing values (>1x/sec) | Every change re-renders ALL consumers | useSyncExternalStore, Zustand, Jotai |
| Large global state (100+ fields) | Single update triggers widespread re-renders | External state library with selectors |
| Animation values | 60fps updates re-render entire consumer tree | CSS variables, refs, animation libraries |
| Form state across many fields | Each keystroke re-renders all field consumers | React Hook Form, Formik |
---
Reference Links
- references/examples.md -- Complete context patterns with TypeScript
- references/anti-patterns.md -- Common context mistakes and fixes
Official Sources
- https://react.dev/reference/react/createContext
- https://react.dev/reference/react/useContext
- https://react.dev/reference/react/use
- https://react.dev/learn/passing-data-deeply-with-context
- https://react.dev/learn/scaling-up-with-reducer-and-context
Context Anti-Patterns
Common mistakes when using React Context, with explanations and fixes.
---
Anti-Pattern 1: Unstable Value Object
Problem: Creating a new object in the Provider render body causes ALL consumers to re-render on every parent render, even when the actual values have not changed.
// BAD -- new object reference every render
function UserProvider({ children }: { children: ReactNode }): JSX.Element {
const [user, setUser] = useState<User | null>(null);
return (
<UserContext.Provider value={{ user, setUser }}>
{children}
</UserContext.Provider>
);
}Why it breaks: React compares context values with Object.is. A new object literal { user, setUser } creates a new reference every render, so Object.is(prevValue, nextValue) returns false even when user has not changed.
Fix: ALWAYS stabilize the value with useMemo:
// GOOD -- stable reference, re-renders only when user changes
function UserProvider({ children }: { children: ReactNode }): JSX.Element {
const [user, setUser] = useState<User | null>(null);
const value = useMemo(() => ({ user, setUser }), [user]);
return (
<UserContext.Provider value={value}>{children}</UserContext.Provider>
);
}---
Anti-Pattern 2: Missing Provider Guard
Problem: Using useContext without checking for a missing Provider returns the defaultValue, which silently hides bugs.
// BAD -- returns null silently, crashes later with confusing error
const AuthContext = createContext<AuthContextType | null>(null);
function ProfilePage(): JSX.Element {
const auth = useContext(AuthContext);
// auth is null if Provider is missing -- crashes on auth.user access
return <span>{auth.user.name}</span>;
}Fix: ALWAYS create a custom hook that throws a descriptive error:
// GOOD -- fails fast with clear message
function useAuth(): AuthContextType {
const context = useContext(AuthContext);
if (context === null) {
throw new Error("useAuth must be used within an AuthProvider");
}
return context;
}
function ProfilePage(): JSX.Element {
const { user } = useAuth(); // throws if AuthProvider is missing
return <span>{user?.name}</span>;
}---
Anti-Pattern 3: Single Giant Context
Problem: Putting all application state into one context causes every consumer to re-render when ANY value changes.
// BAD -- theme change re-renders auth consumers, locale consumers, etc.
interface AppContextType {
user: User | null;
theme: Theme;
locale: string;
notifications: Notification[];
settings: Settings;
}
const AppContext = createContext<AppContextType | null>(null);Why it breaks: When notifications updates, components that only read theme still re-render because the context value reference changed.
Fix: ALWAYS split contexts by concern:
// GOOD -- independent update cycles
const AuthContext = createContext<AuthContextType | null>(null);
const ThemeContext = createContext<ThemeContextType | null>(null);
const LocaleContext = createContext<LocaleContextType | null>(null);
const NotificationContext = createContext<NotificationContextType | null>(null);---
Anti-Pattern 4: Context for Frequently Changing Values
Problem: Using Context for values that change many times per second (mouse position, scroll, animation) causes the entire consumer subtree to re-render at that frequency.
// BAD -- re-renders ALL consumers 60 times per second
const MouseContext = createContext<{ x: number; y: number }>({ x: 0, y: 0 });
function MouseTracker({ children }: { children: ReactNode }): JSX.Element {
const [pos, setPos] = useState({ x: 0, y: 0 });
useEffect(() => {
function handleMove(e: MouseEvent): void {
setPos({ x: e.clientX, y: e.clientY }); // triggers re-render cascade
}
window.addEventListener("mousemove", handleMove);
return () => window.removeEventListener("mousemove", handleMove);
}, []);
return (
<MouseContext.Provider value={pos}>{children}</MouseContext.Provider>
);
}Fix: Use useSyncExternalStore or an external store with selectors:
// GOOD -- only subscribers to specific values re-render
import { useSyncExternalStore } from "react";
const mouseStore = {
position: { x: 0, y: 0 },
listeners: new Set<() => void>(),
subscribe(listener: () => void): () => void {
this.listeners.add(listener);
return () => this.listeners.delete(listener);
},
getSnapshot(): { x: number; y: number } {
return this.position;
},
};
window.addEventListener("mousemove", (e) => {
mouseStore.position = { x: e.clientX, y: e.clientY };
mouseStore.listeners.forEach((l) => l());
});
function useMousePosition(): { x: number; y: number } {
return useSyncExternalStore(
mouseStore.subscribe.bind(mouseStore),
mouseStore.getSnapshot.bind(mouseStore)
);
}---
Anti-Pattern 5: Reading Context in the Same Component as Provider
Problem: useContext searches UPWARD from the calling component. The Provider in the same component does NOT affect that component's own useContext call.
// BAD -- useTheme reads from PARENT provider, not this component's provider
function App(): JSX.Element {
const theme = useTheme(); // reads from ABOVE, not below
return (
<ThemeContext.Provider value="dark">
<Page />
</ThemeContext.Provider>
);
}Fix: Move the consumer into a child component:
// GOOD -- consumer is below the provider
function App(): JSX.Element {
return (
<ThemeContext.Provider value="dark">
<ThemedApp />
</ThemeContext.Provider>
);
}
function ThemedApp(): JSX.Element {
const { theme } = useTheme(); // correctly reads "dark"
return <Page className={theme} />;
}---
Anti-Pattern 6: Using Context.Consumer
Problem: The render prop pattern (Context.Consumer) is legacy and verbose.
// BAD -- legacy pattern, verbose, hard to compose
function Header(): JSX.Element {
return (
<ThemeContext.Consumer>
{(theme) => (
<AuthContext.Consumer>
{(auth) => (
<header className={theme}>
Welcome, {auth.user?.name}
</header>
)}
</AuthContext.Consumer>
)}
</ThemeContext.Consumer>
);
}Fix: ALWAYS use useContext hook:
// GOOD -- clean, composable
function Header(): JSX.Element {
const { theme } = useTheme();
const { user } = useAuth();
return (
<header className={theme}>
Welcome, {user?.name}
</header>
);
}---
Anti-Pattern 7: Prop Drilling Through Context
Problem: Using Context to pass data that only goes one or two levels deep. Context adds complexity without benefit for shallow trees.
// BAD -- unnecessary context for shallow tree
const ButtonColorContext = createContext<string>("blue");
function Card(): JSX.Element {
return (
<ButtonColorContext.Provider value="red">
<CardBody />
</ButtonColorContext.Provider>
);
}
function CardBody(): JSX.Element {
const color = useContext(ButtonColorContext);
return <Button color={color} />;
}Fix: Pass props directly for shallow hierarchies:
// GOOD -- simple prop passing for 1-2 levels
function Card(): JSX.Element {
return <CardBody buttonColor="red" />;
}
function CardBody({ buttonColor }: { buttonColor: string }): JSX.Element {
return <Button color={buttonColor} />;
}Rule of thumb: If the data passes through fewer than 3 levels, prefer props over Context.
---
Anti-Pattern 8: Duplicate Module Instances
Problem: If the module containing createContext is bundled twice (e.g., different versions in node_modules), the Provider and Consumer reference DIFFERENT context objects.
Symptoms:
useContextreturns the default value despite Provider being present- No error messages -- silently fails
Fix:
- Ensure single module instance in your bundler configuration
- Use
npm ls reactto check for duplicate React installations - In monorepos, hoist shared dependencies to the root
---
Anti-Pattern 9: Missing value Prop on Provider
Problem: Rendering a Provider without a value prop passes undefined to consumers, overriding the defaultValue from createContext.
// BAD -- passes undefined, NOT the defaultValue from createContext
<ThemeContext.Provider>
<App />
</ThemeContext.Provider>Why: defaultValue from createContext is ONLY used when there is NO Provider in the tree at all. A Provider without value explicitly passes undefined.
Fix: ALWAYS pass an explicit value prop:
// GOOD
<ThemeContext.Provider value={theme}>
<App />
</ThemeContext.Provider>---
Summary Table
| Anti-Pattern | Impact | Severity |
|---|---|---|
| Unstable value object | All consumers re-render every parent render | High |
| Missing provider guard | Silent null bugs, confusing runtime errors | High |
| Single giant context | Unrelated components re-render together | Medium |
| Frequently changing values | Performance degradation, janky UI | High |
| Same-component provider/consumer | Reads wrong context value | Medium |
| Using Context.Consumer | Verbose code, callback hell | Low |
| Context for shallow props | Unnecessary complexity | Low |
| Duplicate module instances | Silent context mismatch | High |
| Missing value prop | Consumers receive undefined | Medium |
Context API Examples
Complete, copy-paste-ready patterns for React Context with TypeScript.
---
Example 1: Theme Context (Simple Toggle)
import {
createContext,
useCallback,
useContext,
useMemo,
useState,
type ReactNode,
} from "react";
// Types
type Theme = "light" | "dark";
interface ThemeContextType {
theme: Theme;
toggleTheme: () => void;
}
// Context
const ThemeContext = createContext<ThemeContextType | null>(null);
// Custom hook
function useTheme(): ThemeContextType {
const context = useContext(ThemeContext);
if (context === null) {
throw new Error("useTheme must be used within a ThemeProvider");
}
return context;
}
// Provider
function ThemeProvider({ children }: { children: ReactNode }): JSX.Element {
const [theme, setTheme] = useState<Theme>("light");
const toggleTheme = useCallback(() => {
setTheme((prev) => (prev === "light" ? "dark" : "light"));
}, []);
const value = useMemo<ThemeContextType>(
() => ({ theme, toggleTheme }),
[theme, toggleTheme]
);
return (
<ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>
);
}
// Consumer component
function ThemeToggle(): JSX.Element {
const { theme, toggleTheme } = useTheme();
return (
<button onClick={toggleTheme}>
Current theme: {theme}
</button>
);
}
// App
function App(): JSX.Element {
return (
<ThemeProvider>
<ThemeToggle />
</ThemeProvider>
);
}---
Example 2: Auth Context (Async Operations)
import {
createContext,
useCallback,
useContext,
useEffect,
useMemo,
useState,
type ReactNode,
} from "react";
// Types
interface User {
id: string;
name: string;
email: string;
}
interface AuthContextType {
user: User | null;
isLoading: boolean;
login: (email: string, password: string) => Promise<void>;
logout: () => Promise<void>;
}
// Context
const AuthContext = createContext<AuthContextType | null>(null);
// Custom hook
function useAuth(): AuthContextType {
const context = useContext(AuthContext);
if (context === null) {
throw new Error("useAuth must be used within an AuthProvider");
}
return context;
}
// Provider
function AuthProvider({ children }: { children: ReactNode }): JSX.Element {
const [user, setUser] = useState<User | null>(null);
const [isLoading, setIsLoading] = useState<boolean>(true);
// Check session on mount
useEffect(() => {
let ignore = false;
async function checkSession(): Promise<void> {
try {
const session = await api.getSession();
if (!ignore) {
setUser(session.user);
}
} catch {
if (!ignore) {
setUser(null);
}
} finally {
if (!ignore) {
setIsLoading(false);
}
}
}
checkSession();
return () => {
ignore = true;
};
}, []);
const login = useCallback(async (email: string, password: string) => {
const result = await api.login(email, password);
setUser(result.user);
}, []);
const logout = useCallback(async () => {
await api.logout();
setUser(null);
}, []);
const value = useMemo<AuthContextType>(
() => ({ user, isLoading, login, logout }),
[user, isLoading, login, logout]
);
return (
<AuthContext.Provider value={value}>{children}</AuthContext.Provider>
);
}
// Guard component
function RequireAuth({ children }: { children: ReactNode }): JSX.Element {
const { user, isLoading } = useAuth();
if (isLoading) {
return <LoadingSpinner />;
}
if (user === null) {
return <Navigate to="/login" />;
}
return <>{children}</>;
}---
Example 3: Context + useReducer (Todo App)
import {
createContext,
useContext,
useMemo,
useReducer,
type Dispatch,
type ReactNode,
} from "react";
// Types
interface Todo {
id: number;
text: string;
done: boolean;
}
interface TodosState {
todos: Todo[];
}
type TodoAction =
| { type: "ADD"; text: string }
| { type: "TOGGLE"; id: number }
| { type: "DELETE"; id: number }
| { type: "CLEAR_COMPLETED" };
type TodosDispatch = Dispatch<TodoAction>;
// Reducer
function todosReducer(state: TodosState, action: TodoAction): TodosState {
switch (action.type) {
case "ADD":
return {
...state,
todos: [
...state.todos,
{ id: Date.now(), text: action.text, done: false },
],
};
case "TOGGLE":
return {
...state,
todos: state.todos.map((t) =>
t.id === action.id ? { ...t, done: !t.done } : t
),
};
case "DELETE":
return {
...state,
todos: state.todos.filter((t) => t.id !== action.id),
};
case "CLEAR_COMPLETED":
return {
...state,
todos: state.todos.filter((t) => !t.done),
};
}
}
const initialState: TodosState = { todos: [] };
// Split contexts for performance
const TodosStateContext = createContext<TodosState | null>(null);
const TodosDispatchContext = createContext<TodosDispatch | null>(null);
// Custom hooks
function useTodosState(): TodosState {
const context = useContext(TodosStateContext);
if (context === null) {
throw new Error("useTodosState must be used within a TodosProvider");
}
return context;
}
function useTodosDispatch(): TodosDispatch {
const context = useContext(TodosDispatchContext);
if (context === null) {
throw new Error("useTodosDispatch must be used within a TodosProvider");
}
return context;
}
// Provider
function TodosProvider({ children }: { children: ReactNode }): JSX.Element {
const [state, dispatch] = useReducer(todosReducer, initialState);
return (
<TodosStateContext.Provider value={state}>
<TodosDispatchContext.Provider value={dispatch}>
{children}
</TodosDispatchContext.Provider>
</TodosStateContext.Provider>
);
}
// Consumer: reads state (re-renders when todos change)
function TodoList(): JSX.Element {
const { todos } = useTodosState();
return (
<ul>
{todos.map((todo) => (
<TodoItem key={todo.id} todo={todo} />
))}
</ul>
);
}
// Consumer: reads dispatch only (NEVER re-renders from state changes)
function AddTodoForm(): JSX.Element {
const dispatch = useTodosDispatch();
const [text, setText] = useState("");
function handleSubmit(e: React.FormEvent): void {
e.preventDefault();
if (text.trim()) {
dispatch({ type: "ADD", text: text.trim() });
setText("");
}
}
return (
<form onSubmit={handleSubmit}>
<input value={text} onChange={(e) => setText(e.target.value)} />
<button type="submit">Add</button>
</form>
);
}---
Example 4: Composing Multiple Providers
// Pattern: Provider composer to avoid nesting hell
interface ProviderProps {
children: ReactNode;
}
type ProviderComponent = React.ComponentType<ProviderProps>;
function ComposeProviders({
providers,
children,
}: {
providers: ProviderComponent[];
children: ReactNode;
}): JSX.Element {
return providers.reduceRight<JSX.Element>(
(acc, Provider) => <Provider>{acc}</Provider>,
<>{children}</>
);
}
// Usage
function App(): JSX.Element {
return (
<ComposeProviders
providers={[AuthProvider, ThemeProvider, LocaleProvider, TodosProvider]}
>
<MainLayout />
</ComposeProviders>
);
}---
Example 5: React 19 -- use(Context) in Conditionals
import { use, createContext } from "react";
const FeatureFlagContext = createContext<Record<string, boolean>>({});
// React 19 ONLY -- use() can be called conditionally
function FeatureGate({
flag,
children,
fallback,
}: {
flag: string;
children: ReactNode;
fallback?: ReactNode;
}): JSX.Element {
// ALLOWED: use() inside a conditional
const flags = use(FeatureFlagContext);
if (flags[flag]) {
return <>{children}</>;
}
return <>{fallback ?? null}</>;
}
// With useContext this would require unconditional call:
function FeatureGateReact18({
flag,
children,
fallback,
}: {
flag: string;
children: ReactNode;
fallback?: ReactNode;
}): JSX.Element {
// useContext MUST be called unconditionally
const flags = useContext(FeatureFlagContext);
if (flags[flag]) {
return <>{children}</>;
}
return <>{fallback ?? null}</>;
}---
Example 6: React 19 -- Context as Provider
// React 19: Context renders directly as a provider
function App(): JSX.Element {
const [theme, setTheme] = useState<"light" | "dark">("light");
const value = useMemo(() => ({ theme, setTheme }), [theme]);
// No .Provider needed in React 19
return (
<ThemeContext value={value}>
<Page />
</ThemeContext>
);
}
// React 18: must use .Provider
function AppReact18(): JSX.Element {
const [theme, setTheme] = useState<"light" | "dark">("light");
const value = useMemo(() => ({ theme, setTheme }), [theme]);
return (
<ThemeContext.Provider value={value}>
<Page />
</ThemeContext.Provider>
);
}---
Example 7: Locale Context with Nested Override
interface LocaleContextType {
locale: string;
t: (key: string) => string;
}
const LocaleContext = createContext<LocaleContextType | null>(null);
function useLocale(): LocaleContextType {
const context = useContext(LocaleContext);
if (context === null) {
throw new Error("useLocale must be used within a LocaleProvider");
}
return context;
}
function LocaleProvider({
locale,
children,
}: {
locale: string;
children: ReactNode;
}): JSX.Element {
const translations = useTranslations(locale);
const value = useMemo<LocaleContextType>(
() => ({
locale,
t: (key: string) => translations[key] ?? key,
}),
[locale, translations]
);
return (
<LocaleContext.Provider value={value}>{children}</LocaleContext.Provider>
);
}
// Nested override: admin panel uses English regardless of app locale
function App(): JSX.Element {
return (
<LocaleProvider locale="nl">
<MainContent />
<LocaleProvider locale="en">
<AdminPanel /> {/* Always English */}
</LocaleProvider>
</LocaleProvider>
);
}