I have read about render props extensively on the official React documentation as well as other articles. However, I am trying to do something similar to what Tailwind does and am failing to figure out how they use this pattern in their components to expose state information about a component.
For example, if you have a look at their Switch component. Its usage is as follows:
function MyToggle() {
const [enabled, setEnabled] = useState(false)
return (
<Switch checked={enabled} onChange={setEnabled} as={Fragment}>
{({ checked }) => (
/* Use the `checked` state to conditionally style the button. */
<button
className={`${
checked ? 'bg-blue-600' : 'bg-gray-200'
} relative inline-flex h-6 w-11 items-center rounded-full`}
>
<span className="sr-only">Enable notifications</span>
<span
className={`${
checked ? 'translate-x-6' : 'translate-x-1'
} inline-block h-4 w-4 transform rounded-full bg-white transition`}
/>
</button>
)}
</Switch>
)
}
If I were to write the Switch component from scratch, I would instantiate a state for checked
in the component function but how do I make it so that the state is exposed when I use the component in the manner:
<Switch>
{({ checked }) => (
...
)}
</Switch>
From their github,they do something like:
function SwitchFn(props) {
let {
checked,
...theirProps
} = props
let [checked, onChange] = useControllable(controlledChecked, controlledOnChange, defaultChecked)
let slot = useMemo<SwitchRenderPropArg>(() => ({ checked }), [checked])
...
return (
<>
{name != null && checked && (
<Hidden
features={HiddenFeatures.Hidden}
{...compact({
as: 'input',
type: 'checkbox',
hidden: true,
readOnly: true,
form,
checked,
name,
value,
})}
/>
)}
{render({ ourProps, theirProps, slot, defaultTag: DEFAULT_SWITCH_TAG, name: 'Switch' })}
</>
)
}
But it is quite difficult to follow along. Please help. Thank you.
To be able to do
<Switch>
{({ checked }) => (
...
)}
</Switch>
It means that children
is a callback instead of a React Node.
In the component implementation it means that instead of simply calling children
that evaluates to a Node
, you would call children(...)
.
So you could implement Switch with something like
function Switch({children}){
const [checked, setChecked] = useState(false);
...
return (
// it's of course more complex that that, but the core idea is here
<div onClick={() => setChecked(current => !current)}>
{children({checked})}
</div>
);
}
It's a specific case of a render prop, where the prop is children
.
You could ask "But where is children actually called in that Tailwind code???"
switch.tsx
uses utils/render.ts
which extracts children from the props.