How to give rendering control to users with prop getters

October 2nd, 2017 β€” 6 min read

by Annie Spratt
by Annie Spratt
No translations available.Add translation

If you're an egghead.io subscriber, you can also learn about this pattern with these two lessons: Use Prop Collections with Render Props and Use Prop Getters with Render Props

Since I released downshift 🏎 a few weeks ago. Of all things, I think the most common question I've gotten has been about the "prop getters." As far as I know, downshift is the first library to implement this pattern, so I thought I'd explain why it's useful and how to implement it. If you're unfamiliar with downshift, please read the intro post before you continue. Don't worry, I'll wait...

dog wagging the tail while waiting

So, to recap from what you read, prop getters are one piece to the puzzle to let you hand rendering over to the users of your components (a great idea). I got the idea from Jared Forsyth one day at an airport. You can only really use it with the render prop pattern. It's basically a function which will return props when called and people must apply those props to the right element to hook together all the relevant elements to make the overarching component. Hopefully that's clear πŸ˜€

To talk about this, we'll actually use a different component I wrote recently that uses this pattern called react-toggled.

React toggled logo

It's pretty small, so I'm just going to paste all of it here for you (see the syntax highlighted file here):

import {Component} from 'react'
import PropTypes from 'prop-types'

const callAll =
  (...fns) =>
  (...args) =>
    fns.forEach(fn => fn && fn(...args))

class Toggle extends Component {
  static propTypes = {
    defaultOn: PropTypes.bool,
    on: PropTypes.bool,
    onToggle: PropTypes.func,
    children: PropTypes.oneOfType([PropTypes.func, PropTypes.array]).isRequired,
  }
  static defaultProps = {
    defaultOn: false,
    onToggle: () => {},
  }
  state = {
    on: this.getOn({on: this.props.defaultOn}),
  }

  getOn(state = this.state) {
    return this.isOnControlled() ? this.props.on : state.on
  }

  isOnControlled() {
    return this.props.on !== undefined
  }

  getTogglerProps = (props = {}) => ({
    'aria-controls': 'target',
    'aria-expanded': Boolean(this.getOn()),
    ...props,
    onClick: callAll(props.onClick, this.toggle),
  })

  getTogglerStateAndHelpers() {
    return {
      on: this.getOn(),
      getTogglerProps: this.getTogglerProps,
      setOn: this.setOn,
      setOff: this.setOff,
      toggle: this.toggle,
    }
  }

  setOnState = (state = !this.getOn()) => {
    if (this.isOnControlled()) {
      this.props.onToggle(state, this.getTogglerStateAndHelpers())
    } else {
      this.setState({on: state}, () => {
        this.props.onToggle(this.getOn(), this.getTogglerStateAndHelpers())
      })
    }
  }

  setOn = this.setOnState.bind(this, true)
  setOff = this.setOnState.bind(this, false)
  toggle = this.setOnState.bind(this, undefined)

  render() {
    const renderProp = unwrapArray(this.props.children)
    return renderProp(this.getTogglerStateAndHelpers())
  }
}

/**
 * Takes an argument and if it's an array, returns the first item in the array
 * otherwise returns the argument
 * @param {*} arg the maybe-array
 * @return {*} the arg or it's first item
 */

function unwrapArray(arg) {
  return Array.isArray(arg) ? arg[0] : arg
}

export default Toggle

You'll notice that this.props.children is unwrapped, this is for preact compatibility.

And here's how you could use react-toggled:

<Toggle>
  {({on, getTogglerProps}) => (
    <div>
      <button
        {...getTogglerProps({
          onClick() {
            alert('you clicked!')
          },
        })}
      >
        Toggle me
      </button>
      <div>{on ? 'Toggled On' : 'Toggled Off'}</div>
    </div>
  )}
</Toggle>

There are a few neat things about this component I may talk about in a future post, but for now, let's focus on the getTogglerProps function (that's the prop getter).

The cool thing about this pattern is that it allows users to render whatever they want. So your components take care of the hard and generic part (the logic of the component) and the user can take care of the easy and less-generic part: what to show and how it's styled given the state of the component.

So if users want the <div> to appear above the <button> or to not appear at all, then the user can simply do that without having to look up any docs for props or anything. This is pretty powerful!

With that said, the biggest question I get from folks about "prop getters" is:

Why are you using a function to get props? Why not just pass a regular object to my render callback and let me spread that instead of having to call a function?

What people are saying is they'd prefer to do: <button {...togglerProps} {...myOwnProps} /> rather than <button {...getTogglerProps(myOwnProps)} />. I can understand why folks might prefer that. It feels like you have more control that way. However, we're actually doing something useful with this function and the props that you provide...

For this component, we care about the onClick prop you apply to your <button>. We need to call this.toggle. But what if you (as a user of the component) also wanted to have a handler for onClick? You might try to write it like this: <button onClick={this.handleClick} {...togglerProps} />. But you'd find that togglerPropsoverrides your custom onClick handler, so you could switch it to: <button {...togglerProps} onClick={this.handleClick} /> and now you have the opposite problem! Your custom onClick is overriding the onClick from togglerProps, so react-toggled isn't working at all.

With that context, let's see how we avoid this problem by using a function. Check out the implementation of getTogglerProps:

getTogglerProps = (props = {}) => ({
  'aria-controls': 'target',
  'aria-expanded': Boolean(this.getOn()),
  ...props,
  onClick: callAll(props.onClick, this.toggle),
})

You'll notice that the onClick prop is assigned to callAll(props.onClick, this.toggle). The callAll function is pretty simple:

const callAll =
  (...fns) =>
  (...args) =>
    fns.forEach(fn => fn && fn(...args))

It does what it says. Calls all the functions it's given, if they exist. In our case, both of our onClick handlers will be called as we need. (See the transpiled version if you're less accustomed to arrow functions).


To summarize, prop getters are one of the patterns that enable you to hand rendering responsibility to the user of your components (a really awesome idea). You can only really implement it with the render prop pattern (in our case we use the children prop, but you could use a render prop if you prefer).

Here are a few projects that implement the prop getters pattern:

I hope to see more folks doing stuff like this in the future! Good luck to you all! πŸ‘

Epic React

Get Really Good at React

Illustration of a Rocket
Kent C. Dodds
Written by Kent C. Dodds

Kent C. Dodds is a JavaScript software engineer and teacher. Kent'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

If you found this article helpful.

You will love these ones as well.