Render Props

If you haven't already, check out the post on Higher Order Components, which is referenced in this article.

Tools: React

Overview

Render-props is a composition pattern that makes reusing React components many times easier. They are very similar to Higher Order Components with some notable differences; one being that they are quite a bit more configurable.

Think of render-props as an equipment rental store. You can rent methods and states from a particular component when you need it, instead of 'owning' them yourself. In reality, it is just a function called render which is simply passed as props.

Render-props gets its name from how it is commonly used:

<EquipmentStore render={props => <Job {...props}/>}/>

Real World Example

React Router uses the render-props pattern in the <Route /> component to make redirecting easy, while Formik uses it extensively to make dynamic, validatable forms.


Guide

Let's demonstrate how render-props works by starting with a Toggler component from the earlier post on HOCS:

class Toggler extends Component {
  constructor() {
    super()
    this.state = {
      on: false
    }
    this.toggle = this.toggle.bind(this);
  }
  toggle() {
    this.setState(({ on }) => ({ on: !on }))
  }
  render() {
    const { on } = this.state;
    const { component , ...props } = this.props;
    const C = component;
    return (
      <C on={on}toggle={this.toggle}{...props}/>
    )
  }
}

export const withToggler = C => props => <Toggler component={C}{...props}/>

This is a really handy way to to write a toggling effect exactly once throughout an entire app:

export default withToggler(AnyComponentNeedingToggler)

However, we don't have an obvious way to set the initial state of the toggle. We're stuck with setting the on value to either false or true. This could be problematic if in one situation (such as an edit pop-up form) we want it to be false to start, but in another (A login modal) we want it to be true.

This is certainly avoidable by simply using the ! operator inside our component but then we are reinterpreting the meaning of on instead of setting it the appropriate value.

Let's refactor Toggler so that we can use it like a normal component employing render-props:

class Toggler extends Component {
  constructor(props) {
    super(props)
    this.state = {
    //initialize state with the 'on' value from props
      on: props.on
    }
    this.toggle = this.toggle.bind(this);
  }
  toggle() {
    this.setState(({ on }) => ({ on: !on }))
  }
  render() {
  //compile all the methods and state into one object and provide it as an argument to this.props.render
    const props = {
      toggle: this.toggle,
      ...this.state
    }
    return this.props.render(props)
  }
}

//We no longer need the higher order function withToggler

In our application we can now employ Toggler anywhere we want, and still initialize it with a particular on value:

//if there is no equal sign, then JSX will assume a prop value is true
<Toggler on render={({on, toggle}) => (
  <button onClick={toggle}>Menu</button>
  <nav className={on ? "show" : "hide"}>
    <a href="/">Home</a>
    <a href="/about">About</a>
  </nav>
)}/>

Children

You will probably come across a pattern where props.children is used in place of props.render. For example:

    render() {
    const props = {
      toggle: this.toggle,
      ...this.state
    }
    return this.props.children(props)
  }

This simply allows you to provide a function as children instead of React elements.

<Toggler on>
    {({on, toggle}) => (
      <button onClick={toggle}>Menu</button>
      <nav className={on ? "show" : "hide"}>
        <a href="/">Home</a>
        <a href="/about">About</a>
      </nav>
    )}/>
</Toggler>

Forms

I would highly recommend using Formik for dealing with forms, but here is a simplified version of how it works:

export default class Form extends Component {
    constructor(props) {
        super(props);
        this.state = props.inputs;
        this.handleChange = this.handleChange.bind(this);
        this.handleSubmit = this.handleSubmit.bind(this);
    }
    handleChange({target: {name, value, checked, type}}) {
        this.setState({
            [name]: type === "checkbox" ? checked : value
        });
    }
    handleSubmit(e) {
        e.preventDefault();
        this.props.submit(this.state);
        this.props.reset && this.setState(this.props.inputs);
    }
    render() {
        const props = {
            handleChange: this.handleChange,
            handleSubmit: this.handleSubmit,
            inputs: this.state,
        };
        return this.props.render(props)
    }
}

Now you can write forms as simple presentational components receiving props:

const Login = ({handleSubmit, handleChange, inputs}) => {
  return (
    <form onSubmit={handleSubmit}>
      <input type="text"name="username"value={inputs.username}onChange={handleChange}/>
      <input type="password"name="password"value={inputs.password}onChange={handleChange}/>
    </form>
  )
}

Our final implementation is very simple and compact.

<Form 
  reset
  inputs={{username: "", password: ""}
  submit={inputs => postToServer(inputs)}
  render={Login}
  />

Summary

Render-props is a powerful way to make your app dynamic and less verbose. By providing a function as a property of props you can expose a component's inner methods and state to the outside, which effectively lets you 'rent' common functionalities across the component tree.

Exercises

  • TBD

Additional Resources

Render Props - ReactJS Docs
Advanced React Patterns - Kent C. Dodds