An Overview of Render Props in React

Avatar of Kingsley Silas
Kingsley Silas on

📣 Freelancers, Developers, and Part-Time Agency Owners: Kickstart Your Own Digital Agency with UACADEMY Launch by UGURUS 📣

An Overview of Render Props in React

Using render props in React is a technique for efficiently re-using code. According to the React documentation, “a component with a render prop takes a function that returns a React element and calls it instead of implementing its own render logic.” To understand what that means, let’s take a look at the render props pattern and then apply it to a couple of light examples.

The render props pattern

In working with render props, you pass a render function to a component that, in turn, returns a React element. This render function is defined by another component, and the receiving component shares what is passed through the render function.

This is what this looks like:

class BaseComponent extends Component {
  render() {
    return <Fragment>{this.props.render()}</Fragment>;
  }
}

Imagine, if you will, that our App is a gift box where App itself is the bow on top. If the box is the component we are creating and we open it, we’ll expose the props, states, functions and methods needed to make the component work once it’s called by render().

The render function of a component normally has all the JSX and such that form the DOM for that component. Instead, this component has a render function, this.props.render(), that will display a component that gets passed in via props.

Example: Creating a counter

See the Pen React Render Props by Kingsley Silas Chijioke (@kinsomicrote) on CodePen.

Let’s make a simple counter example that increases and decreases a value depending on the button that is clicked.

First, we start by creating a component that will be used to wrap the initial state, methods and rendering. Creatively, we’ll call this Wrapper:

class Wrapper extends Component {
  state = {
    count: 0
  };

  // Increase count
  increment = () => {
    const { count } = this.state;
    return this.setState({ count: count + 1 });
  };

  // Decrease count
  decrement = () => {
    const { count } = this.state;
    return this.setState({ count: count - 1 });
  };

  render() {
    const { count } = this.state;

    return (
      <div>
        {this.props.render({
          increment: this.increment,
          decrement: this.decrement,
          count: count
        })}
      </div>
    );
  }
}

In the Wrapper component, we specify the methods and state what gets exposed to the wrapped component. For this example, we need the increment and decrement methods. We have our default count set as 0. The logic is to either increment or decrement count depending on the method that is triggered, starting with a zero value.

If you take a look at the return() method, you’ll see that we are making use of this.props.render(). It is through this function that we pass methods and state from the Wrapper component so that the component that is being wrapped by it will make use of it.

To use it for our App component, the component will look like this:

class App extends React.Component {
  render() {
    return (
      <Wrapper
        render={({ increment, decrement, count }) => (
          <div>
            <div>
              <h3>Render Props Counter</h3>
            </div>
            <div>
              <p>{count}</p>
              <button onClick={() => increment()}>Increment</button>
              <button onClick={() => decrement()}>Decrement</button>
            </div>
          </div>
        )}
      />
    );
  }
}

Example: Creating a data list

The gain lies in the reusable power of render props, let’s create a component that can be used to handle a list of data which is obtainable from an API.

See the Pen React Render Props 2 by Kingsley Silas Chijioke (@kinsomicrote) on CodePen.

What do we want from the wrapper component this time? We want to pass the source link for the data we want to render to it, then make a GET request to obtain the data. When the data is obtained we then set it as the new state of the component and render it for display.

class Wrapper extends React.Component {
  state = {
    isLoading: true,
    error: null,
    list: []
  };

  fetchData() {
    axios.get(this.props.link)
      .then((response) => {
        this.setState({
          list: response.data,
          isLoading: false
        });
    })
    .catch(error => this.setState({ error, isLoading: false }));
  }

  componentDidMount() {
    this.setState({ isLoading: true }, this.fetchData);
  }

  render() {
    return this.props.render(this.state);
  }
}

The data link will be passed as props to the Wrapper component. When we get the response from the server, we update list using what is returned from the server. The request is made to the server after the component mounts.

Here is how the Wrapper gets used:

class App extends React.Component {
  render() {
    return (
      <Wrapper
        link="https://jsonplaceholder.typicode.com/users"
        render={({ list, isLoading, error }) => (
          <div>
            <h2>Random Users</h2>
            {error ? <p>{error.message}</p> : null}
            {isLoading ? (
              <h2>Loading...</h2>
            ) : (
              <ul>{list.map(user => <li key={user.id}>{user.name}</li>)}</ul>
            )}
          </div>
        )}
      />
    );
  }
}

You can see that we pass the link as a prop, then we use ES6 de-structuring to get the state of the Wrapper component which is then rendered. The first time the component loads, we display loading text, which is replaced by the list of items once we get a response and data from the server.

The App component here is a class component since it does not manage state. We can transform it into a functional stateless component.

const App = () => {
  return (
    <Wrapper
      link="https://jsonplaceholder.typicode.com/users"
      render={({ list, isLoading, error }) => (
        <div>
          <h2>Random Users</h2>
          {error ? <p>{error.message}</p> : null}
          {isLoading ? (
            <h2>Loading...</h2>
          ) : (
            <ul>{list.map(user => <li key={user.id}>{user.name}</li>)}</ul>
          )}
        </div>
      )}
    />
  );
}

That’s a wrap!

People often compare render props with higher-order components. If you want to go down that path, I suggest you check out this post as well as this insightful talk on the topic by Michael Jackson.