DEV Community

Brian Neville-O'Neill
Brian Neville-O'Neill

Posted on • Originally published at blog.logrocket.com on

React Custom Hooks and the death of Render Props

Everyone had something to say about React Hooks when it was introduced last month. But I didn’t see anyone say we can stop using Render Props. So I’ll say it. We can stop using Render Props. In this article, we’ll learn how to do just that.

Note  — Render Props won’t completely die off but they will not be used for what we are currently accustomed to. Also, React Hooks are still in alpha so it is not recommended to use them in production. Please don’t rush to refactor all your existing applications.

What are Render Props?

Render Props are an advanced pattern for sharing logic across components. A component (usually termed as Container Component) can delegate how the UI looks to other components (called Presentation Components) and only implement the business logic itself. So we can implement cross-cutting concerns as components by using Render Props pattern.

Why do we need Render Props?

  1. For sharing code between components
  2. To use the Context API

The problem with Render Props

Using Render Props comes with its own issues. Some of these issues appear only when we dig deeper or when we scale our project.

Wrapper Hell

To increase the DRYness of our codebase we often implement many small, granular components such that each component deals with only one concern. The issue with that is we are often left with a whole lot of wrapper components nested deeply inside one another. If we decrease the number of wrapper components then the component size and complexity increases. Also, the reusability of a wrapper component might decrease too. This tweet perfectly sums it up.

body[data-twttr-rendered="true"] {background-color: transparent;}.twitter-tweet {margin: auto !important;}

@threepointone @kentcdodds I mean come on (screen shot of actual code I'm playing with right now)

 — @acdlite

function notifyResize(height) {height = height ? height : document.documentElement.offsetHeight; var resized = false; if (window.donkey && donkey.resize) {donkey.resize(height); resized = true;}if (parent && parent._resizeIframe) {var obj = {iframe: window.frameElement, height: height}; parent._resizeIframe(obj); resized = true;}if (window.location && window.location.hash === "#amp=1" && window.parent && window.parent.postMessage) {window.parent.postMessage({sentinel: "amp", type: "embed-size", height: height}, "*");}if (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.resize) {window.webkit.messageHandlers.resize.postMessage(height); resized = true;}return resized;}twttr.events.bind('rendered', function (event) {notifyResize();}); twttr.events.bind('resize', function (event) {notifyResize();});if (parent && parent._resizeIframe) {var maxWidth = parseInt(window.frameElement.getAttribute("width")); if ( 500 < maxWidth) {window.frameElement.setAttribute("width", "500");}}

Binding this is a pain

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

Classes are hard for humans as well as machines

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 also. This incurs a double penalty because it neither leads to good DX (developer experience) nor good UX (user experience). React team is even thinking of moving Class Components support to a separate package in the future.

Using PureComponent becomes complicated

Using a render prop can negate the advantage that comes from using PureComponent if we create the function assigned inside the render method. This is because the shallow prop comparison will always return false for new props , and each render, in this case, will generate a new value for the render prop. Refer to the 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 and that’s why we had to use classes in container components for implementing 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 codes.

body[data-twttr-rendered="true"] {background-color: transparent;}.twitter-tweet {margin: auto !important;}

@ericandrewlewis @ReactPodcast @dan_abramov @jaredpalmer between Suspense (anything that needs to be scheduled) and Hooks (effects, context, and local state), there are very few remaining use cases for render props that don't have framework-level aolutions

 — @chantastic

function notifyResize(height) {height = height ? height : document.documentElement.offsetHeight; var resized = false; if (window.donkey && donkey.resize) {donkey.resize(height); resized = true;}if (parent && parent._resizeIframe) {var obj = {iframe: window.frameElement, height: height}; parent._resizeIframe(obj); resized = true;}if (window.location && window.location.hash === "#amp=1" && window.parent && window.parent.postMessage) {window.parent.postMessage({sentinel: "amp", type: "embed-size", height: height}, "*");}if (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.resize) {window.webkit.messageHandlers.resize.postMessage(height); resized = true;}return resized;}twttr.events.bind('rendered', function (event) {notifyResize();}); twttr.events.bind('resize', function (event) {notifyResize();});if (parent && parent._resizeIframe) {var maxWidth = parseInt(window.frameElement.getAttribute("width")); if ( 500 < maxWidth) {window.frameElement.setAttribute("width", "500");}}

What’s even better is that we can implement our own Custom Hooks. These hooks give us a new, easy and powerful primitive for sharing logic across components. That means we don’t need classes or 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.

A basic example of React Hooks

The best way to learn something is by using it. So to use React Hooks, we’ll build a component which by default shows some information and lets us update that information by clicking a button. It will look something like this.

Editable Item with React Hooks (Click here for video)

What we can observe is that the component has two types of states. One state is for controlling the input field and the other state is to toggle between the viewer and the editor. Let’s see how this can be implemented with React Hooks.

Link to code

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

Calling the useState hook gives us an array of two items, the first one is for reading state and the next is for updating that state. We will hold the state for controlled input in the value variable and the state updater function in the setValue variable. By default, 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 editorVisible is false and render the editor when the value is true. Since we want to show the viewer by default, we need to have editorVisible value as false initially. That’s why we pass false while calling useState.

To toggle between the viewer and editor we define a toggleEditor function which sets the editorVisible state to it’s opposite everytime the function is called. As we want to call this function whenever the user clicks on the button, we assign it as onClick prop of the button.

That’s how easy using React Hooks can be but it doesn’t stop here. Hooks have one more trick up their sleeves and that is Custom Hooks. We can see that the editorVisible state is actually a toggler and toggling is a very common use-case in our UIs. If we wanted to share the toggling logic across components, we would define a Toggler component and use 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 Custom Hooks…

Using Custom React Hooks for sharing logic between components

With Custom Hooks we can extract the toggling logic from the EditableItem component into a separate function. We will call this function useToggle as it is recommended to start the name of a custom hook with the word “use”. The useToggle custom hook will look like this:

Link to code

First, we get the state and state updater by using the useState hook. Then we define a toggler function which sets the toggleValue to the opposite of its current value. At last, we return an array of two items, first is toggleValue to read the current state and the next is toggler to toggle the toggleValue state.

Though creating functions at each render is not slow in modern browsers we can avoid that by memoizing the toggler function. For this purpose useCallback hook comes in handy. More details can be found here.

Link to code

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

Link to code

Let’s see how Render Props fare in comparison to React Hooks:

https://medium.com/media/3591dac4e57ed134da7735713ee17b03/hrefhttps://medium.com/media/ad8c63daf90a0e08713436aad3ceb74c/href

No doubt, code reuse between components is easier with Custom Hooks and also require less amount of code. Next, we’ll learn how to consume context data with React Hooks instead of using Render Props pattern.

Using Reacts Hooks for consuming context data

Just like we have useState hook for state, we have useContext for consuming context data. Again, we’ll try to learn it by using it in a practical scenario. Well, it’s a common requirement to have user details available across components. This is a great use-case for context hooks:

Use Context to change user (Click here for video)

Here we have two components UserProfile and ChangeProfile. UserProfile component shows the details of the user and the ChangeProfile component is used for switching between users.

Note —  The switching between users part is just for our demo. In real projects, instead of the select menu, we will update user details based on who logs in.

The implementation might look like this:

https://medium.com/media/4a7b03b04eab8e0469bfc52b921b543a/href

We have made a separate component called User for storing user state and providing data and data update method to UserContext.Provider. These are then consumed by its children components UserProfile and ChangeProfile. The UserProfile component only needs to read 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 is enough to show that using context is also very simple with Custom Hooks.

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

Link to code

Well, there is a simpler way, one that doesn’t need functions as props. We can just assign JSX as component props like this:

Link to code

Using Render Props pattern here would be a mistake because its intended use is to share data between components. So, in this case, we should avoid using Render Props.

My personal opinion is that Render Props pattern wasn’t intended for the above use cases but the community had to use 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. Michael Chan has made a video explaining that.

https://medium.com/media/6c3846b43caf82f8fc45379ae0d87446/href

Conclusion

It’s clear that the future of React is very bright. The React team focus is crystal clear. If anyone knows of other use-cases for Render Props let me know, I’ll surely mention them in the article.

If you like my work, please follow me on Twitter and Medium or subscribe to my newsletter.

Plug: LogRocket, a DVR for web apps

https://logrocket.com/signup/

LogRocket is a frontend logging tool that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.

In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single page apps.

Try it for free.


Top comments (0)