What is a render prop?
A render prop is a component that renders their children as functions to pass a managed state as its argument. This pattern allows components to manage their own state, hide its implementation, and pass it down the component tree without lifting the state up.
Example use case
A good use case for renderProps
are modals. In this example we will create a SignUpModal
component to understand where renderProps might be useful.
Here we have our Modal
component and it will take open
, and setOpen
as props.
const Modal = ({ open, closeModal, children }) => {
const background = open ? 'green' : 'red'
return (
<div
style={{ background }}
onClick={closeModal}
>
{children}
</div>
);
}
And here we have a SignUpModal
that uses our Modal
component.
const SignUpModal = () => {
const [firstName, setFirstName] = useState('')
const [laststName, setLaststName] = useState('')
const [open, setOpen] = useState(false)
const closeModal = () => setOpen(!open)
return (
<Modal open={open} closeModal={closeModal}>
I'm {open ? "Open": "Closed"}
<button onClick={closeModal}>x</button>
First name: <input value={firstName} onChange={...} />
Last name: <input value={lastName} onChange={...} />
</Modal>
);
}
See how the states of the Modal
mangles along the states of the SignUpModal
component itself. This is a bad pattern it makes our logic look needlessly complex, and tangled. This is where the renderProps
pattern comes in handy.
Let's refactor SignUpModal
, and Modal
to use the renderProps
pattern.
const Modal = ({ children }) => {
const background = open ? 'green' : 'red'
const [open, setOpen] = useState(false)
const closeModal = () => setOpen(!open)
return (
<div
style={{ background }}
onClick={closeModal}
>
{children({ open, closeModal })}
</div>
);
}
And this is our SignUpModal
using the new Modal
component above.
const SignUpModal = () => {
const [firstName, setFirstName] = useState('')
const [laststName, setLaststName] = useState('')
return (
<Modal>
{({ open, closeModal }) => (
<>
I'm {open ? "Open": "Closed"}
<button onClick={closeModal}>x</button>
First name: <input value={firstName} onChange={...} />
Last name: <input value={lastName} onChange={...} />
</>
)}
</Modal>
);
}
Look at the clean separation of concerns between Modal, and SignUpModal. Both components now manages their own state without mangling from each other.
The renderProps pattern can also be used to override/modify certain parts of a component. A use case I always use it for is when I want a button to have an icon. Like the example below.
const colors = ['#FAD534', '#1B1B1B']
const Button = ({ renderIcon, children }) => {
// Randomly select a color from colors array
const randomColor = colors[Math.floor(Math.random() * colors.length)]
return (
<button>
{renderIcon(randomColor)} {children}
</button>
)
}
const Page = () => {
return (
<div>
Get started
<Button
renderIcon={(color) => (
<svg fill={color} width='1em' height='1em' viewBox='0 0 24 24'>
<path
fill='currentColor'
d='M8.59 16.58L13.17 12L8.59 7.41L10 6l6 6l-6 6l-1.41-1.42Z'
/>
</svg>
)}
>
Click here
</Button>
</div>
)
}
Conclusion
The renderProp pattern is a neat trick to avoid leaky states from your components. Also makes your components cohesive, and concise.