The renderProp pattern in React

What's a render prop, and when you should use it.

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.

Modal.js
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.

SignUpModal.js
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.

Modal.js
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.

SignUpModal.js
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.