This site runs best with JavaScript enabled.

React Hooks: What's going to happen to render props?

December 10, 2018


What am I going to do with all these render props components now that react hooks solve the code reuse problem better than render props ever did?

Current Available Translations:

About a year ago, I published "How to give rendering control to users with prop getters". In that post, I show the entire implementation (at the time) of react-toggled which I actually built for the sole purpose of teaching some of the patterns that I used in downshift. It's a much smaller and simpler component that implements many of the same patterns as downshift so it served as a great way to teach the prop getters pattern.

Both react-toggled and downshift use the render prop pattern as a mechanism for React component logic code sharing. As I explained in my blog post "When to NOT use Render Props", that's the primary use case for the render prop pattern. But that's also the primary use case for React Hooks as well. And React Hooks are WAY simpler than class components + render props.

So does that mean that when React Hooks are stable you wont need render props at all anymore? No! I can think of two scenarios where the render prop pattern will still be very useful, and I'll share those with you in a moment. But let's go ahead and establish my claim that hooks are simpler by comparing the current version of react-toggled with a hooks-based implementation.

If you're interested, here's the current source for react-toggled.

Here's a typical usage of react-toggled:

1function App() {
2 return (
3 <Toggle>
4 {({on, toggle}) => <button onClick={toggle}>{on ? 'on' : 'off'}</button>}
5 </Toggle>
6 )
7}

If all we wanted was simple toggle functionality, our hook version would be:

1function useToggle(initialOn = false) {
2 const [on, setOn] = useState(initialOn)
3 const toggle = () => setOn(!on)
4 return {on, toggle}
5}

Then people could use that like so:

1function App() {
2 const {on, toggle} = useToggle()
3 return <button onClick={toggle}>{on ? 'on' : 'off'}</button>
4}

Cool! A lot simpler! But the Toggle component in react-toggled actually supports a lot more. For one thing, it provides a helper called getTogglerProps which will give you the correct props you need for a toggler to work (including ariaattributes for accessibility). So let's make that work:

1// returns a function which calls all the given functions
2const callAll = (...fns) => (...args) => fns.forEach(fn => fn && fn(...args))
3
4function useToggle(initialOn = false) {
5 const [on, setOn] = useState(initialOn)
6 const toggle = () => setOn(!on)
7 const getTogglerProps = (props = {}) => ({
8 'aria-expanded': on,
9 tabIndex: 0,
10 ...props,
11 onClick: callAll(props.onClick, toggle),
12 })
13 return {
14 on,
15 toggle,
16 getTogglerProps,
17 }
18}

And now our useToggle hook can use the getTogglerProps:

1function App() {
2 const {on, getTogglerProps} = useToggle()
3 return <button {...getTogglerProps()}>{on ? 'on' : 'off'}</button>
4}

And it's more accessible and stuff. Neat right? Well, what if I don't need the getTogglerProps for my use case? Let's split this up a bit:

1// returns a function which calls all the given functions
2const callAll = (...fns) => (...args) => fns.forEach(fn => fn && fn(...args))
3
4function useToggle(initialOn = false) {
5 const [on, setOn] = useState(initialOn)
6 const toggle = () => setOn(!on)
7 return {on, toggle}
8}
9
10function useToggleWithPropGetter(initialOn) {
11 const {on, toggle} = useToggle(initialOn)
12 const getTogglerProps = (props = {}) => ({
13 'aria-expanded': on,
14 tabIndex: 0,
15 ...props,
16 onClick: callAll(props.onClick, toggle),
17 })
18 return {on, toggle, getTogglerProps}
19}

And we could do the same thing to support the getInputTogglerProps and getElementTogglerProps helpers that react-toggled currently supports. This would actually allow us to easily tree-shake out those extra utilities that our app is not using, something that would be pretty unergonomic to do with a render props solution (not impossible, just kinda ugly).

But Kent! I don't want to go and refactor all the places in my app that use the render prop API to use the new hooks API!!

Never fear! Check this out:

1const Toggle = ({children, ...props}) => children(useToggle(props))

There's your render prop component. You can use that just like you were using the old one and migrate over time. In fact, this is how I recommend testing custom hooks!

There's a little more to this (like how do we port the control props pattern to react hooks for example). I'm going to leave that to you to discover. Once you've tried it out for a little bit, then watch me do it. There's a catch with the way we've been testing things a bit that change slightly with hooks (thanks to JavaScript closures).

The remaining use case for render props

Ok, so we can refactor our components to use hooks, and even continue to export react components with a render prop-based API (you might be interested, you may even consider going all out with the hydra pattern). But let's imagine we're now in a future where we don't need render props for logic reuse and everyone's using hooks. Is there any reason to continue writing or using components that expose a render props API?

YES! Observe! Here's an example of using downshift with react-virtualized. Here's the relevant bit:

1<List
2 // ... some props
3 rowRenderer={({key, index, style}) => (
4 <div
5 // ... some props
6 />
7 )}
8/>

Checkout that rowRenderer prop there. Do you know what that is? IT'S A RENDER PROP! What!? 🙀 That's inversion of control in all its glory with render props right there. That's a prop that react-virtualized uses to delegate control of rendering rows in a list to you the user of the component. If react-virtualized were to be rewritten to use hooks, maybe it could accept the rowRenderer as an argument to the useVirtualized hook, but I don't really see any benefit to that over it's current API. So I think render props (and this style of inversion of control) are here to stay for use cases like this.

Conclusion

I hope you find this interesting and helpful. Remember that React hooks are still in alpha and subject to change. They are also completely opt-in and will not require any breaking changes to React's API. I think that's a great thing. Don't go rewriting your apps! Refactor them (once hooks are stable)!

Good luck!

Learn more about Refactoring to React Hooks from me:

My new egghead.io course will show you how to refactor a typical app's components to use react hooks (and React.lazy/suspense). It's a good time!

Also, check out this free egghead playlist about hooks and suspense!

Things to not miss:

Discuss on TwitterEdit post on GitHub

Share article
Kent C. Dodds

Kent C. Dodds is a JavaScript software engineer and teacher. He's taught hundreds of thousands of people how to make the world a better place with quality software development tools and practices. He lives with his wife and four kids in Utah.

Learn more with Kent C. Dodds:

  • Learn React Hooks: Join me in this remote workshop. I'll teach you the ins and outs of React Hooks. Tickets are limited! 🎟
  • TestingJavaScript.com: Jump on this self-paced workshop and learn the smart, efficient way to test any JavaScript application. 🏆