A render prop is a function prop that lets a component share behavior while its caller decides what to render. Implement ToggleRenderProps, a headless React component that owns one boolean and calls its child with the current value plus stable controls. The component must add no wrapper element.
function ToggleRenderProps(props: {
initialOn?: unknown;
children: (api: {
on: boolean;
toggle: () => void;
setOn: React.Dispatch<React.SetStateAction<boolean>>;
reset: () => void;
}) => React.ReactNode;
}): React.ReactNode;
The child owns the button and its label:
React.createElement(
ToggleRenderProps,
{ initialOn: true },
({ on, toggle }) =>
React.createElement('button', { onClick: toggle }, on ? 'Mute' : 'Unmute'),
);
// first render: <button>Mute</button>
// after one click: <button>Unmute</button>
The same behavior can drive completely different markup:
React.createElement(ToggleRenderProps, null, ({ on, setOn, reset }) =>
React.createElement('section', null,
React.createElement('output', null, on ? 'open' : 'closed'),
React.createElement('button', { onClick: () => setOn(true) }, 'Open'),
React.createElement('button', { onClick: reset }, 'Reset'),
),
);
initialOn with Boolean once on mount. Later prop changes must neither overwrite live state nor change what reset restores.toggle, setOn, and reset keep the same identities across renders. toggle must use a functional update so calls in one batch compose.{ on, toggle, setOn, reset }. Replacing that function must not reset state.TypeError if children is not a function.ToggleRenderProps owns the state and control semantics, then hands its child complete control over the rendered markup.
Some behavior belongs together even when its visual presentations do not. A menu, disclosure, and mute button can all need the same boolean transitions, but forcing them through one hard-coded component also forces one DOM structure. A render prop separates those decisions: the component owns behavior, while a function supplied by the caller returns the elements.
The details matter. The initial prop is only a mount-time baseline, controls should not change identity on every render, and rapid toggles must compose instead of reading one stale value.
Treat the component as a headless state machine. It produces an API object rather than markup; the child function consumes that object and produces the only DOM React receives.
The direct version appears to work for one click:
function ToggleRenderProps({ initialOn = false, children }) {
const [on, setOn] = React.useState(Boolean(initialOn));
return children({
on,
toggle: () => setOn(!on),
setOn,
reset: () => setOn(Boolean(initialOn)),
});
}
Every render allocates new toggle and reset functions. More importantly, toggle closes over one rendered value, so multiple calls before React renders again all request the same next state. reset also follows the latest prop instead of the first mount baseline.
const React = require('react');
function ToggleRenderProps({ initialOn = false, children }) {
if (typeof children !== 'function') {
throw new TypeError('ToggleRenderProps children must be a function.');
}
// useRef keeps the first mount's baseline even if the prop later changes.
const initialRef = React.useRef(Boolean(initialOn));
const [on, setOn] = React.useState(() => initialRef.current);
// Functional state reads the latest queued value, so batched calls compose.
const toggle = React.useCallback(() => {
setOn((current) => !current);
}, []);
const reset = React.useCallback(() => {
setOn(initialRef.current);
}, []);
return children({ on, toggle, setOn, reset });
}
module.exports = { ToggleRenderProps };
The ref captures the baseline once. React's state setter is already stable, while empty-dependency callbacks make toggle and reset stable too. The child is not memoized or stored: calling the current function during render guarantees a replacement child sees the live state immediately.
useState updater functions receive the pending state, not the value captured by one render. Three queued toggles therefore calculate false → true → false → true. useCallback keeps each command's identity stable because neither command needs a changing render value.
Mount ToggleRenderProps with a truthy initialOn value and a button child. The initial coercion becomes true; the ref and state both receive that value. The child runs with on: true and returns a button labelled on.
Now call setOn(false). React stores false and renders again, calling the latest child with on: false. If the parent changes initialOn to false, nothing resets: the ref still holds the first true. Calling reset() reads that ref, restores true, and produces one more child render.
setOn(!on). Several toggles in one batch all read the same captured on. Use setOn(current => !current) so each update receives the previous queued result.reset. That silently changes the meaning of reset after a parent rerender. Capture Boolean(initialOn) in a ref on the first mount.on, so it must change when the state changes. Stable command functions are the useful guarantee; a new four-field object is correct.div changes layout and DOM semantics.setOn value. The public setter follows React's boolean-state contract, including updater functions. Only the initial prop is coerced.onChange(nextOn) notification by keeping the latest callback in a ref, while preserving stable control identities.Keep practising the same patterns with a nearby challenge.
No submissions yet
Share your approach and start the discussion.
A render prop is a function prop that lets a component share behavior while its caller decides what to render. Implement ToggleRenderProps, a headless React component that owns one boolean and calls its child with the current value plus stable controls. The component must add no wrapper element.
function ToggleRenderProps(props: {
initialOn?: unknown;
children: (api: {
on: boolean;
toggle: () => void;
setOn: React.Dispatch<React.SetStateAction<boolean>>;
reset: () => void;
}) => React.ReactNode;
}): React.ReactNode;
The child owns the button and its label:
React.createElement(
ToggleRenderProps,
{ initialOn: true },
({ on, toggle }) =>
React.createElement('button', { onClick: toggle }, on ? 'Mute' : 'Unmute'),
);
// first render: <button>Mute</button>
// after one click: <button>Unmute</button>
The same behavior can drive completely different markup:
React.createElement(ToggleRenderProps, null, ({ on, setOn, reset }) =>
React.createElement('section', null,
React.createElement('output', null, on ? 'open' : 'closed'),
React.createElement('button', { onClick: () => setOn(true) }, 'Open'),
React.createElement('button', { onClick: reset }, 'Reset'),
),
);
initialOn with Boolean once on mount. Later prop changes must neither overwrite live state nor change what reset restores.toggle, setOn, and reset keep the same identities across renders. toggle must use a functional update so calls in one batch compose.{ on, toggle, setOn, reset }. Replacing that function must not reset state.TypeError if children is not a function.ToggleRenderProps owns the state and control semantics, then hands its child complete control over the rendered markup.
Some behavior belongs together even when its visual presentations do not. A menu, disclosure, and mute button can all need the same boolean transitions, but forcing them through one hard-coded component also forces one DOM structure. A render prop separates those decisions: the component owns behavior, while a function supplied by the caller returns the elements.
The details matter. The initial prop is only a mount-time baseline, controls should not change identity on every render, and rapid toggles must compose instead of reading one stale value.
Treat the component as a headless state machine. It produces an API object rather than markup; the child function consumes that object and produces the only DOM React receives.
The direct version appears to work for one click:
function ToggleRenderProps({ initialOn = false, children }) {
const [on, setOn] = React.useState(Boolean(initialOn));
return children({
on,
toggle: () => setOn(!on),
setOn,
reset: () => setOn(Boolean(initialOn)),
});
}
Every render allocates new toggle and reset functions. More importantly, toggle closes over one rendered value, so multiple calls before React renders again all request the same next state. reset also follows the latest prop instead of the first mount baseline.
const React = require('react');
function ToggleRenderProps({ initialOn = false, children }) {
if (typeof children !== 'function') {
throw new TypeError('ToggleRenderProps children must be a function.');
}
// useRef keeps the first mount's baseline even if the prop later changes.
const initialRef = React.useRef(Boolean(initialOn));
const [on, setOn] = React.useState(() => initialRef.current);
// Functional state reads the latest queued value, so batched calls compose.
const toggle = React.useCallback(() => {
setOn((current) => !current);
}, []);
const reset = React.useCallback(() => {
setOn(initialRef.current);
}, []);
return children({ on, toggle, setOn, reset });
}
module.exports = { ToggleRenderProps };
The ref captures the baseline once. React's state setter is already stable, while empty-dependency callbacks make toggle and reset stable too. The child is not memoized or stored: calling the current function during render guarantees a replacement child sees the live state immediately.
useState updater functions receive the pending state, not the value captured by one render. Three queued toggles therefore calculate false → true → false → true. useCallback keeps each command's identity stable because neither command needs a changing render value.
Mount ToggleRenderProps with a truthy initialOn value and a button child. The initial coercion becomes true; the ref and state both receive that value. The child runs with on: true and returns a button labelled on.
Now call setOn(false). React stores false and renders again, calling the latest child with on: false. If the parent changes initialOn to false, nothing resets: the ref still holds the first true. Calling reset() reads that ref, restores true, and produces one more child render.
setOn(!on). Several toggles in one batch all read the same captured on. Use setOn(current => !current) so each update receives the previous queued result.reset. That silently changes the meaning of reset after a parent rerender. Capture Boolean(initialOn) in a ref on the first mount.on, so it must change when the state changes. Stable command functions are the useful guarantee; a new four-field object is correct.div changes layout and DOM semantics.setOn value. The public setter follows React's boolean-state contract, including updater functions. Only the initial prop is coerced.onChange(nextOn) notification by keeping the latest callback in a ref, while preserving stable control identities.Keep practising the same patterns with a nearby challenge.
No submissions yet
Share your approach and start the discussion.