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

December 10th, 2018 6 min read

Photo by [Nadine Shaabana](https://unsplash.com/photos/anXB3AhQcJ0)
Photo by [Nadine Shaabana](https://unsplash.com/photos/anXB3AhQcJ0)

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:

function App() {
  return (
    <Toggle>
      {({on, toggle}) => <button onClick={toggle}>{on ? 'on' : 'off'}</button>}
    </Toggle>
  )
}

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

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

Then people could use that like so:

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

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:

// returns a function which calls all the given functions
const callAll =
  (...fns) =>
  (...args) =>
    fns.forEach(fn => fn && fn(...args))

function useToggle(initialOn = false) {
  const [on, setOn] = useState(initialOn)
  const toggle = () => setOn(!on)
  const getTogglerProps = (props = {}) => ({
    'aria-expanded': on,
    tabIndex: 0,
    ...props,
    onClick: callAll(props.onClick, toggle),
  })
  return {
    on,
    toggle,
    getTogglerProps,
  }
}

And now our useToggle hook can use the getTogglerProps:

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

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:

// returns a function which calls all the given functions
const callAll =
  (...fns) =>
  (...args) =>
    fns.forEach(fn => fn && fn(...args))

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

function useToggleWithPropGetter(initialOn) {
  const {on, toggle} = useToggle(initialOn)
  const getTogglerProps = (props = {}) => ({
    'aria-expanded': on,
    tabIndex: 0,
    ...props,
    onClick: callAll(props.onClick, toggle),
  })
  return {on, toggle, getTogglerProps}
}

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:

const 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:

<List
  // ... some props
  rowRenderer={({key, index, style}) => (
    <div
    // ... some props
    />
  )}
/>

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!

Kent C. Dodds
Written by 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 about Kent

Want to learn more?

Join Kent in a live workshop

If you found this article helpful.

You will love these ones as well.