A context provider stores a value above a React subtree so descendants can read it without prop drilling. Implement ThemeProvider and useTheme to share a theme string, React's state setter, and a stable toggle function. The provider must also keep its value object stable when the theme has not changed, which prevents context consumers from receiving a needless new value.
function ThemeProvider({
initialTheme?: string, // defaults to 'light'
children: React.ReactNode,
}): React.ReactNode;
function useTheme(): {
theme: string;
setTheme: React.Dispatch<React.SetStateAction<string>>;
toggleTheme: () => void;
};
<ThemeProvider initialTheme="dark">
<Toolbar />
</ThemeProvider>
// Inside Toolbar:
const { theme, toggleTheme } = useTheme();
// theme === 'dark'; toggleTheme() changes it to 'light'.
const { setTheme } = useTheme();
setTheme('sepia');
setTheme((current) => `${current}-contrast`);
// The final theme is 'sepia-contrast'.
initialTheme; changing that prop later must not reset current state.{ theme, setTheme, toggleTheme }; setTheme must be React's setter and accept direct values or updater functions.dark becomes light; every other current string becomes dark. Use a functional state update so batched calls compose.toggleTheme stable and memoize the context value. Its object identity changes only when theme changes.useTheme throws a clear error outside ThemeProvider; nested providers remain independent and the nearest one wins.The provider keeps theme state local to one subtree and exposes a small, stable interface to its descendants.
A toolbar several levels below the app shell needs the current theme and a way to change it. Passing those values through every component couples unrelated layers to theme state. React context lets a provider publish one value to any descendant that asks for it.
Treat each provider as the boundary of a theme zone. Descendants read the closest zone, while components outside every zone receive an error instead of an ambiguous fallback.
function ThemeProvider({ initialTheme = 'light', children }) {
const [theme, setTheme] = React.useState(initialTheme);
const toggleTheme = () => setTheme(theme === 'dark' ? 'light' : 'dark');
const value = { theme, setTheme, toggleTheme };
return React.createElement(ThemeContext.Provider, { value }, children);
}
This renders the right value at first, but it creates a new toggle function and context object on every provider render. Its toggle also closes over one rendered value, so two calls batched before the next render both calculate from the same stale theme. Consumers receive needless identity changes even when the theme stays put.
const React = require('react');
const ThemeContext = React.createContext(undefined);
function ThemeProvider({ initialTheme = 'light', children }) {
const [theme, setTheme] = React.useState(initialTheme);
const toggleTheme = React.useCallback(() => {
setTheme((current) => (current === 'dark' ? 'light' : 'dark'));
}, []);
const value = React.useMemo(
() => ({ theme, setTheme, toggleTheme }),
[theme, toggleTheme],
);
return React.createElement(ThemeContext.Provider, { value }, children);
}
function useTheme() {
const value = React.useContext(ThemeContext);
if (value === undefined) {
throw new Error('useTheme must be used within a ThemeProvider');
}
return value;
}
module.exports = { ThemeProvider, useTheme };
The functional updater reads the current queued state, so consecutive toggles compose even when React batches them. useCallback preserves the toggle's identity, and React already preserves the identity of setTheme. useMemo can therefore reuse the whole context object until theme changes.
Mount <ThemeProvider initialTheme="light"> around a toolbar. useState captures light on the first mount, and useTheme returns { theme: 'light', setTheme, toggleTheme }. Call toggleTheme() twice in one event: the first updater receives light and queues dark; the second receives that queued dark and returns light. Because the final state equals the starting state, the memoized public value remains valid.
Now place a second provider with initialTheme="dark" around one nested panel. That panel reads and updates the inner state; its sibling still reads the outer light state.
undefined as a sentinel and throw in useTheme.initialTheme in an effect — an effect keyed on the prop resets user changes whenever the parent rerenders with a new value; pass it only to useState.theme in the toggle — batched toggles all read the same render snapshot; use setTheme(current => ...) so each update receives the queued result.{ theme, setTheme, toggleTheme } is a new object on every provider render; memoize it after stabilizing the functions.<div> and should not alter layout; return the context provider around children directly.prefers-color-scheme when no explicit choice exists and subscribe to system changes.Keep practising the same patterns with a nearby challenge.
No submissions yet
Share your approach and start the discussion.
A context provider stores a value above a React subtree so descendants can read it without prop drilling. Implement ThemeProvider and useTheme to share a theme string, React's state setter, and a stable toggle function. The provider must also keep its value object stable when the theme has not changed, which prevents context consumers from receiving a needless new value.
function ThemeProvider({
initialTheme?: string, // defaults to 'light'
children: React.ReactNode,
}): React.ReactNode;
function useTheme(): {
theme: string;
setTheme: React.Dispatch<React.SetStateAction<string>>;
toggleTheme: () => void;
};
<ThemeProvider initialTheme="dark">
<Toolbar />
</ThemeProvider>
// Inside Toolbar:
const { theme, toggleTheme } = useTheme();
// theme === 'dark'; toggleTheme() changes it to 'light'.
const { setTheme } = useTheme();
setTheme('sepia');
setTheme((current) => `${current}-contrast`);
// The final theme is 'sepia-contrast'.
initialTheme; changing that prop later must not reset current state.{ theme, setTheme, toggleTheme }; setTheme must be React's setter and accept direct values or updater functions.dark becomes light; every other current string becomes dark. Use a functional state update so batched calls compose.toggleTheme stable and memoize the context value. Its object identity changes only when theme changes.useTheme throws a clear error outside ThemeProvider; nested providers remain independent and the nearest one wins.The provider keeps theme state local to one subtree and exposes a small, stable interface to its descendants.
A toolbar several levels below the app shell needs the current theme and a way to change it. Passing those values through every component couples unrelated layers to theme state. React context lets a provider publish one value to any descendant that asks for it.
Treat each provider as the boundary of a theme zone. Descendants read the closest zone, while components outside every zone receive an error instead of an ambiguous fallback.
function ThemeProvider({ initialTheme = 'light', children }) {
const [theme, setTheme] = React.useState(initialTheme);
const toggleTheme = () => setTheme(theme === 'dark' ? 'light' : 'dark');
const value = { theme, setTheme, toggleTheme };
return React.createElement(ThemeContext.Provider, { value }, children);
}
This renders the right value at first, but it creates a new toggle function and context object on every provider render. Its toggle also closes over one rendered value, so two calls batched before the next render both calculate from the same stale theme. Consumers receive needless identity changes even when the theme stays put.
const React = require('react');
const ThemeContext = React.createContext(undefined);
function ThemeProvider({ initialTheme = 'light', children }) {
const [theme, setTheme] = React.useState(initialTheme);
const toggleTheme = React.useCallback(() => {
setTheme((current) => (current === 'dark' ? 'light' : 'dark'));
}, []);
const value = React.useMemo(
() => ({ theme, setTheme, toggleTheme }),
[theme, toggleTheme],
);
return React.createElement(ThemeContext.Provider, { value }, children);
}
function useTheme() {
const value = React.useContext(ThemeContext);
if (value === undefined) {
throw new Error('useTheme must be used within a ThemeProvider');
}
return value;
}
module.exports = { ThemeProvider, useTheme };
The functional updater reads the current queued state, so consecutive toggles compose even when React batches them. useCallback preserves the toggle's identity, and React already preserves the identity of setTheme. useMemo can therefore reuse the whole context object until theme changes.
Mount <ThemeProvider initialTheme="light"> around a toolbar. useState captures light on the first mount, and useTheme returns { theme: 'light', setTheme, toggleTheme }. Call toggleTheme() twice in one event: the first updater receives light and queues dark; the second receives that queued dark and returns light. Because the final state equals the starting state, the memoized public value remains valid.
Now place a second provider with initialTheme="dark" around one nested panel. That panel reads and updates the inner state; its sibling still reads the outer light state.
undefined as a sentinel and throw in useTheme.initialTheme in an effect — an effect keyed on the prop resets user changes whenever the parent rerenders with a new value; pass it only to useState.theme in the toggle — batched toggles all read the same render snapshot; use setTheme(current => ...) so each update receives the queued result.{ theme, setTheme, toggleTheme } is a new object on every provider render; memoize it after stabilizing the functions.<div> and should not alter layout; return the context provider around children directly.prefers-color-scheme when no explicit choice exists and subscribe to system changes.Keep practising the same patterns with a nearby challenge.
No submissions yet
Share your approach and start the discussion.