Aditya Agarwal Loves experimenting on the web. You can follow me on Twitter @hackerrank.

React render props vs. custom Hooks

8 min read 2390

React Render Props Vs. Custom Hooks

Editor’s note: This post was last updated on 16 August 2021 to include updated technology changes and various other updates. However, it may still contain information that is out of date.

Everyone had something to say about React Hooks when they released, but developers still continued to use render props. However, developers can stop using render props because of custom Hooks, and in this article, we’ll learn how to do that.

But, note that render props haven’t and won’t completely die off, but they have evolved to provide different functionalities.

To run the examples in this post, use Create React App v4.0.3 and React v17.0.3 or above.

What are render props?

Render props are an advanced pattern for sharing logic across components. A component, usually termed as a container component, can delegate how a UI looks to other presentation components and only implement business logic.

This means we can implement cross-cutting concerns as components by using render prop patterns.

The overall purposes of using render props are:

  1. Sharing code between components
  2. Using the Context API

Are render props bad?

Using render props comes with their own issues. Some of these issues appear only when we dig deeper or when we scale a project.

Render props’ issues with wrappers

To increase the DRYness of our codebase, we often implement many small, granular components so each component deals with a single concern. However, this often leaves developers with many wrapper components nested deeply inside one another.

If we increase the number of wrapper components, the component size and complexity increase while the reusability of a wrapper component might decrease. Andrew Clark perfectly summed up the issue on Twitter:

We made a custom demo for .
No really. Click here to check it out.

Andrew Clark on Twitter: “I mean come on (screen shot of actual code I’m playing with right now) pic.twitter.com/Ucc8gaxPMp / Twitter”

I mean come on (screen shot of actual code I’m playing with right now) pic.twitter.com/Ucc8gaxPMp

Render props’ issues with binding this

Since the wrapper components deal with state or lifecycle methods, they use class components. With class components, we must bind this properly, otherwise, we risk losing the this context inside functions. The syntax for binding all methods looks ugly and is often a burden for developers.

Render props’ issues with classes

Classes come with a good amount of boilerplate code, which is awful for us to write every time we convert a functional component into a class component.

Turns out, classes are hard to optimize by our build tools, as well. This incurs a double penalty because it neither leads to a good developer experience nor a good user experience. The React team is even thinking of moving class components support to a separate package in the future.

What are the problems of using render props with pure components?

Using a render prop can negate the advantage that comes from using PureComponent if we create a function assigned inside the render method.

This is because the shallow prop comparison always returns false for new props, and, in this case, each render generates a new value for the render prop. Refer to the React docs for more details.

Many of these problems are not entirely the fault of the render props pattern, though. Until now, React did not provide a way of using state or lifecycle methods without involving classes. That’s why we must use classes in container components for implementing the render props pattern.

However, all of that changes with the introduction of the React Hooks API. React Hooks lets us use state and lifecycle Hooks inside functional components with only a few lines of code.

What’s better, we can implement our own custom Hooks. These Hooks give us an easy and powerful primitive for sharing logic across components. That means we don’t need classes or a render props pattern to share code between components.

Before jumping into that, let’s first get a good look at how React Hooks can be used.

What are React Hooks?

As a short description, React Hooks let you use state and other features within functional components without writing a class component. However, the best way to learn more about something is by using it.

So, to use React Hooks, we’ll build a component that shows information by default and lets us update that information by clicking a button.

Component Shows Information In A Field And We Can Update The Information By Pressing The Button To The Right Of The Field

What’s a React Hook editable item?

What we can observe from this example is that the component has two types of states. One state controls the input field and the other toggles between the viewer and the editor. Let’s see how this can be implemented with React Hooks:

import React, { useState } from "react";

function EditableItem({ label, initialValue }) {
  const [value, setValue] = useState(initialValue);
  const [editorVisible, setEditorVisible] = useState(false);

  const toggleEditor = () => setEditorVisible(!editorVisible);

  return (
    <main>
      {editorVisible ? (
        <label>
          {label}
          <input
            type="text"
            value={value}
            onChange={event => setValue(event.target.value)}
          />
        </label>
      ) : (
        <span>{value}</span>
      )}
      <button onClick={toggleEditor}>{editorVisible ? "Done" : "Edit"}</button>
    </main>
  );
}

We defined the EditableItem functional component, which takes two props, label and initialValue props for showing the label above the input field, and the initialValue prop for showing the default info.

Calling the useStateHook gives us an array of two items: one for reading state and the other for updating that state. We must hold the state for controlled input in the value variable and the state updater function in the setValue variable.

By default, the value variable will be assigned the initialValue prop data.

Next, we hold the state for toggling between viewer and editor in the editorVisible variable and its updater in the setEditorVisible variable. In the markup, we render the viewer when the value of editorVisibleis false and the editor when the value is true.

Since we want to show the viewer by default, we must set the editorVisible value as false initially. That’s why we pass false while calling useState.

To toggle between the viewer and editor, we must define the toggleEditor function, which sets the editorVisible state to its opposite when calling the function. As we want to call this function whenever the user clicks on the button, we assign it as the button’s onClick prop.

That’s how easy using React Hooks can be, but it doesn’t stop here. Hooks have one more trick: custom Hooks.

What are custom Hooks in React?

Custom Hooks in React are mechanisms that reuse stateful logic, according to the React docs.

In our use case, we can see that the editorVisible state is a toggler, and toggling is a common use case in our UIs. If we want to share the toggling logic across components, we can define a Toggler component and use the render props pattern to share the toggling method.

But, wouldn’t it be easier if we could just have a function for that instead of messing with components? Enter React custom Hooks.

With custom Hooks, we can extract the toggling logic from the EditableItem component into a separate function. We can call this function useToggle because it is recommended to start the name of a custom Hook with use. The useToggle custom Hook looks like this:

import React, { useState } from "react";

function useToggle(initialValue) {
  const [toggleValue, setToggleValue] = useState(initialValue);
  const toggler = () => setToggleValue(!toggleValue);

  return [toggleValue, toggler];
}

First, we get the state and state updater by using the useState Hook. We then define a toggler function that sets the toggleValue to the opposite of its current value.

At last, we return an array of two items: toggleValue to read the current state and toggler to toggle the toggleValue state.

Though creating functions at each render is not slow in modern browsers, we can avoid it by memoizing the toggler function. For this purpose, the useCallback Hook comes in handy:

import React, { useState, useCallback } from "react";

function useToggle(initialValue) {
  const [toggleValue, setToggleValue] = useState(initialValue);
  const toggler = useCallback(() => setToggleValue(!toggleValue));

  return [toggleValue, toggler];
}

Custom Hooks are used just like any other Hook. This means using useToggle in our EditableItem component is as easy as this:

import React, { useState } from "react";

import useToggle from 'useToggle.js';

function EditableItem({ label, initialValue }) {
  const [value, setValue] = useState(initialValue);
  const [editorVisible, toggleEditorVisible] = useToggle(false);

  return (
    <main>
      {editorVisible ? (
        <label>
          {label}
          <input
            type="text"
            value={value}
            onChange={event => setValue(event.target.value)}
          />
        </label>
      ) : (
        <span>{value}</span>
      )}
      <button onClick={toggleEditorVisible}>
        {editorVisible ? "Done" : "Edit"}
      </button>
    </main>
  );
}

Now, let’s see how render props fare in comparison to React Hooks:

class Toggler extends Component {
  constructor(props) {
    super(props);
    this.state = {
      toggleValue: props.initialValue
    };
    this.toggler = this.toggler.bind(this);
  }

  toggler() {
    this.setState(prevState => ({
      toggleValue: !prevState.toggleValue
    }));
  }
  render() {
    return this.props.children(this.state.toggleValue, this.toggler);
  }
}

class EditableItem extends Component {
  constructor(props) {
    super(props);
    this.state = {
      value: props.initialValue
    };
  }

  setValue(newValue) {
    this.setState({
      value: newValue
    });
  }

  render() {
    return (
      <Toggler initialValue={false}>
        {(editorVisible, toggleEditorVisible) => (
          <main>
            {editorVisible ? (
              <label>
                {this.props.label}
                <input
                  type="text"
                  value={this.state.value}
                  onChange={event => this.setValue(event.target.value)}
                />
              </label>
            ) : (
              <span>{this.state.value}</span>
            )}
            <button onClick={toggleEditorVisible}>
              {editorVisible ? "Done" : "Edit"}
            </button>
          </main>
        )}
      </Toggler>
    );
  }
}

Reusing code with React custom Hooks

No doubt, reusing code between components is easier with custom Hooks and requires less coding. We can then reuse code with the render props pattern:

function useToggle(initialValue) {
  const [toggleValue, setToggleValue] = useState(initialValue);
  const toggler = useCallback(() => setToggleValue(!toggleValue));

  return [toggleValue, toggler];
}

function EditableItem({ label, initialValue }) {
  const [value, setValue] = useState(initialValue);
  const [editorVisible, toggleEditorVisible] = useToggle(false);

  return (
    <main>
      {editorVisible ? (
        <label>
          {label}
          <input
            type="text"
            value={value}
            onChange={event => setValue(event.target.value)}
          />
        </label>
      ) : (
        <span>{value}</span>
      )}
      <button onClick={toggleEditorVisible}>
        {editorVisible ? "Done" : "Edit"}
      </button>
    </main>
  );
}

Next, we’ll learn how to consume context data with React Hooks instead of using the render props pattern.

Consuming context data with React custom Hooks

Just like we have useState Hook for state, we have useContext for consuming context data. Again, we’ll try to learn by using it in a practical scenario.

It’s a common requirement to have user details available across components. This is a great use case for context Hooks:

Using Context Hooks To Share User Information Across Components, Seen By Selecting Different Users In Dropdown Menu

Use context to change users

Here we have two components: UserProfile and ChangeProfile. The UserProfile component shows user details and the ChangeProfile component switches between users.

The switching between users is only applicable for our demo. In real projects, instead of the select menu, we must update user details based on who logs in.

Implementing this looks like the following:

import React, { createContext, useState, useContext } from "react";

const UserContext = createContext();

function UserProfile() {
  const [user] = useContext(UserContext);
  const emailLink = `mailto:${user.email}`;

  return (
    <section>
      <h3>{user.name}</h3>
      <a href={emailLink} title={emailLink}>
        {user.email}
      </a>
    </section>
  );
}

function ChangeProfile() {
  const profiles = [
    {
      name: "Aditya",
      email: "[email protected]"
    },
    {
      name: "Arnold",
      email: "[email protected]"
    }
  ];

  const [user, setUser] = useContext(UserContext);
  const updateUser = event => {
    const profile = profiles[event.target.value];
    setUser(profile);
  };

  return (
    <select onChange={updateUser}>
      {profiles.map((profile, index) => (
        <option value={index} key={profile.email}>
          {profile.name}
        </option>
      ))}
    </select>
  );
}

function User({ children }) {
  const userState = useState({
    name: "Aditya",
    email: "[email protected]"
  });

  return (
    <UserContext.Provider value={userState}>{children}</UserContext.Provider>
  );
}

function App() {
  return (
    <div className="App">
      <User>
        <ChangeProfile />
        <UserProfile />
      </User>
    </div>
  );
}

We have made a separate component called User for storing user state and providing data; UserContext.Provider provides the data update method. These are then consumed by its child components UserProfile and ChangeProfile.

The UserProfile component only reads the user details so we only destructure the first item from the array returned by useContext.

The ChangeProfile component has an array of profiles. When the user selects one profile, we update the UserContext by using the setUser method provided by UserContext itself.

This example shows how using context is very simple with custom Hooks.

Implementing slots in components

There is one more thing for which people sometimes use the render props pattern. That is for implementing slots in their components like this:

function Card({ title, body, action}) {
  return (
    <section className='card'>
      <nav className='header'>
        {title()}
      </nav>
      <main className='main'>
        {body()}
      </main>
      <footer className='footer'>
        {action()}
      </footer>
    </section>
  )
}

function App() {
  return (
    <Card
      title={() => (
        <h2>Card Title</h2>
      )}
      body={() => (
        <div>
          <p>
            Some Content
          </p>
          <a href="/link">Some Link</a>
        </div>
      )}
      action={() => (
        <button onClick={() => console.log('clicked')}>Some Action</button>
      )}
    />
  )
}

Well, there is a simpler way that doesn’t need functions as props. Instead, we can assign JSX as a component prop like this:

function Card({ title, body, action }) {
  return (
    <section className="card">
      <nav className="header">{title}</nav>
      <main className="main">{body}</main>
      <footer className="footer">{action}</footer>
    </section>
  );
}

function App() {
  return (
    <Card
      title={<h2>Card Title</h2>}
      body={
        <div>
          <p>Some Content</p>
          <a href="/link">Some Link</a>
        </div>
      }
      action={
        <button onClick={() => console.log("clicked")}>Some Action</button>
      }
    />
  );
}

Using the render props pattern here would be a mistake because it’s intended to share data between components. So, in this case, we should avoid using render props.

Conclusion

My personal opinion is that the render props pattern wasn’t intended for the above use cases but the community used it because there was no other way. It’s great that the React team took note and made something we will all love to use.

Hooks and render props can co-exist because each has a different role.

It’s clear that the future of React is very bright and the React team’s focus is crystal clear.
If you like my work, please follow me on Twitter and Medium or subscribe to my newsletter.

Full visibility into production React apps

Debugging React applications can be difficult, especially when users experience issues that are hard to reproduce. If you’re interested in monitoring and tracking Redux state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, try LogRocket.

LogRocket is like a DVR for web apps, recording literally everything that happens on your React app. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app's performance, reporting with metrics like client CPU load, client memory usage, and more.

The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.

Modernize how you debug your React apps — .

Aditya Agarwal Loves experimenting on the web. You can follow me on Twitter @hackerrank.

Testing accessibility with Storybook

One big challenge when building a component library is prioritizing accessibility. Accessibility is usually seen as one of those “nice-to-have” features, and unfortunately, we’re...
Laura Carballo
4 min read

4 Replies to “React render props vs. custom Hooks”

  1. Do you have the git repository for this? It’s not clear for me where the setUser is created here…

  2. “Classes are hard for humans as well as machines” How did you said this term? Because developers who have used to work in OOPS programming languages (like java, c++) doesn’t face the difficulty to understand CLASS. Can you explain it please?

  3. Good examples and code in this article. Thanks for sharing your thoughts, and keep up the great work 🙌

Leave a Reply