BT

Facilitating the Spread of Knowledge and Innovation in Professional Software Development

Write for InfoQ

Topics

Choose your language

InfoQ Homepage Presentations From Mixins to Custom Hooks: History of Sharing in React

From Mixins to Custom Hooks: History of Sharing in React

Bookmarks
24:57

Summary

Ben Ilegbodu takes a history lesson on sharing in React in order to better understand how modern day custom hooks work and the problems they solve.

Bio

Ben Ilegbodu has 15 years of professional experience developing user interfaces for the Web. He currently is a Principal Front-end Engineer at Stitch Fix on their Front-end Platform team, helping architect their Design System and build out their front-end.

About the conference

QCon Plus is a virtual conference for senior software engineers and architects that covers the trends, best practices, and solutions leveraged by the world's most innovative software organizations.

Transcript

Ilegbodu: I am super excited to be a part of QCon sharing about sharing in React. We're basically going to go on a six-year journey with React, from mixins in the beginning to hooks right now. React's approach to building UIs makes sharing and reuse great. React is especially great at sharing UI. Here we have a slideshow component. No doubt, it has lots of markup, styling, and UI logic. We can easily use it in two components. We have the players component on the left, that has a slideshow of team players pictures. Then on the right, we have a teams component to have a slideshow of team logos.

Sharing Utilities and Non-Visual Logic

Because React is basically just JavaScript, sharing helper functions is great too. We can call API utilities, data transformation helpers, and all sorts of different functions within our React component code. The tricky part is the stateful non-visual logic aspect. Here we've got the players and teams components again, but this time, they're rendering a different UI. On the left, we have the slideshow component. On the right, we have the ImageList component. They can both call the same API. They both need to maintain state images or logos. Both of them actually need to call the API again, when the page changes. They have similar state and similar logic, but different UI. We want to be able to share that state and share that UI so that whenever someone paginates through the user interface, or logos, it'll all work. We can put this data in a slideshow, in a list view, in a grid view, or whatever. We don't want to repeat that stateful non-visual logic. What I want to do is take us on a journey of how we've tried to solve this problem over React's lifetime.

Background

My name is Ben Ilegbodu. I am a principal front-end engineer at Stitch Fix, and also a Google Developer Expert and Microsoft MVP in web technologies.

Mixins

The first approach to solve this problem was via mixins. This was for React 0.14 and before. We used React.createClass to create class components. It took in optional mixins property to define one or more mixins. Here, we have an ImagesMixin. In this case, the ImagesMixin defines the images and curPage state variables. These are then passed to the slideshow component. Finally, we have this, this.handleNextPage helper, which is called whenever the slideshow changes. This helper method is also defined by the mixin that we'll see. As you can see, there's some magic happening. The state is magically available. We have to know that this handleNextPage helper method is provided.

Here's what the ImagesMixin looks like. It's gnarly to look at. Let's walk through it. First, again, is the images and curPage state that are defined, and getInitialState. Then we use componentDidMount and componentDidUpdate to retrieve the images initially, and when the curPage state or the teamId prop change. We call this updateImages helper that we've defined there. This is where we actually make the fetchImages call, the API call to get our data. Then we set the images state with the return data. Then finally, at the bottom, we define and provide this handleNextPage helper for the components. This is for updating the current page. It's actually not called anywhere within the mixin. It's for the components to call whenever they see fit. This right here, this is the stateful non-visual logic that we're trying to share. This is it. It's state. It's lifecycle methods, and it's the event handler all together. It's not one simple thing we can put in a function, so we seem. How do we share that?

Got You, With Mixins

Mixins worked ok, but there were several got you with them. For one, ECMAScript classes didn't support mixins, and the React team wanted to move to ECMAScript classes instead of maintaining their own class system. Secondly, mixins inherently relied on indirection and agreements in order to work. A mixin might need the component to define a helper method or property, or vice versa, like we just saw. Furthermore, when there were multiple mixins, it was hard to know where exactly the state was coming from, making debugging difficult. Lastly, also, there were helper methods that could have name collisions. We would have to namespace the methods in order to ensure that they were unique per mixin. Just a little difficult there.

Higher-order Components (HOCs) React 15

Because of all of these issues, React 15 migrated to ECMAScript classes in about April 2016. They ditched mixin support. This was a big deal. There was even an official blog post on the React blog entitled, "Mixins considered harmful." Without mixins, we needed a new strategy for solving this problem, though. This problem of sharing stateful non-visual logic. The next pattern that became popular was higher-order components, or HOCs. This pattern was actually first created way back in React 0.13, in February 2015. It was popularized by Dan Abramov later, this was even before he started working at Facebook. An HOC still is a function that takes an existing component, and it returns another component that wraps the original component. We have this withImages, it's the HOC. It takes the component as the parameter. The convention was to prefix HOCs with, With, at the beginning, that's the common convention. It now returns a new class component, which here I'm calling images. It becomes, basically, a wrapper class component.

This images component has all the same state and all the same lifecycle methods as the mixins approach. The big difference is that the HOC renders the component that was passed in instead of mixing stuff in. It passes along all of the existing props that have been passed to it, because remember, it's a wrapper component. Then it adds in to the props, the state properties that it's maintaining, as well as the handleNextPage callback function prop that it's passing, to be able to create the loop again. This HOC, as we can see, is used similar to a mixin, except the component is receiving props instead of state. The exported component is the actual component that will be used in the other UIs. You don't actually get to touch the players component. By wrapping players with images, the HOC is now doing all of the work to maintain the state and the lifecycle methods. Then, lastly, we have this, update this pagination function that we can pass which we're calling handleNextPage that's passed to it.

Got You, With HOCs

HOCs were ok, but they inherited the same problems as mixins, but with different flavors, basically. One, indirection was still a problem. With HOCs, the only communication is through the props. With multiple HOCs, we now don't know where our props are coming from. Is it this HOC or is it that HOC? Then, secondly, there could be prop name collisions if multiple HOCs try to set the same prop, and debugging can be really difficult. Then, lastly, there's no way to alter or configure how the HOCs are composed together, based upon the components props, or any state that it's maintaining itself. This last one wasn't always a problem, but it did make complex situations more challenging than it needed to be.

Render Props

Render props are the third thing that I want to talk about. Render props have actually always existed in React, and they still do now. They were made popular by Michael Jackson, from React training, as a superior replacement for HOCs that we just learned about. This was right around when React 16 was released, so September 2017. Instead of a special mixin, or a function like HOCs, we now have a normal component. I'm calling it images here. It has all the same state and all the same lifecycle methods as the HOC and the mixin approaches. The key piece here is this function prop that's called render. It's a special function prop of the component. Instead of being your typical callback handler, like on click or on change, it's a function that takes in data and returns JSX. It can actually be called anything really. Render is a common convention, hence, render prop. It's also common to use the children prop as well, so children prop as a function. Within render, the render method now, we call the render prop, and we pass it all the data the consumer of images will need. We pass it the images and curPage state properties that we're maintaining, as well as the handleNext callback for updating the curPage state, as we can see here.

Then the use of the render prop feels like normal React. The images component basically is exposing all of its state to players, this component that we have here. By calling the render prop function that's passed to it, we get to indirectly dig into the state. The players component here can now render whatever UI it likes. It's all based upon the data it receives from this render prop. Here, just like in all the previous examples we've already gone through, it's rendering this slideshow component. Any other UI that relies on this data argument that's passed to the render prop, would have to go inside of images as well.

Got You, With Render Props

Render props are really great. They're a great solution to this problem, and just great in general, but there are still a couple of pain points with them. The first one being prop types. React prop types only have PropTypes.func, there is no public definition of what parameters the function will pass. What's the shape of this translation argument that's being passed? What properties are on Auth data? Is Theme an object or is it a single value? You don't know just from looking at the prop types definition. This can all be pretty tricky to figure out without something like TypeScript that provides more strict validation and typing. Also, we can see here that when there are multiple render props, things can get pretty crazy. With render props, the entire UI is nested inside of the function Prop, like we just saw before, and like we're seeing here. Now here, because we have three render props, we're six levels indented before we even start any real UI, which can get crazy. Overall, though, render props worked well for sharing non-visual logic, solving this problem.

Custom Hooks

We've talked about Mixins, then we talked about HOCs. We just talked about render props. Then, with React 16.8 in about February 2019, finally came custom hooks. They were designed specifically to solve this problem of sharing stateful non-visual logic. Custom hooks are functions that have to start with the keyword, use. This one, we're calling it useImages. They always have to start with use. It's one of the rules of hooks. It maintains the state for images and curPage just like all the others, but now we're using the useState hook for maintaining that state. Then, it fetches new images whenever the current page changes as well. We're using the useEffect hook, which seems a little bit simpler just by gazing at it, even if you didn't even know hooks. Then finally, at the bottom, we return the data that the consuming component would need. We're returning the images state. We're returning the curPage state. Then, setCurPage, this function that will update the current page whenever it's called. It's like all the same things before, state, lifecycles, event handler, but it's more clear and more concise, in my opinion. It could all fit entirely on this screen, without any troubles. You remember that gnarly mixin code that we saw in the beginning? Compare that to this.

Our same players component calls our useImages custom hook. It gets the images, the curPage data, as well as the setCurPage function out of the custom hook. It passes all of those three onto the slideshow component, just here. This is pretty similar in spirit to the render prop, but the useImages custom hook looks like a normal function call. It has all the typical inputs and outputs that functions have to get data. It's also separated from the UI up top, so we don't get that sometimes hard to parse UI nesting that we were getting with render props.

A few minutes ago we had our Teams component as well. Instead of rendering a slideshow component we want to render an ImageList component. That was how we went down this whole journey. We can use the same data we get back from useImages to render this ImageList. We call the useImages custom hook just like before, but this time we pass the data to the ImageList component, so to the slideshow component. We were now able to share the same state management, the same lifecycle, same API calls, same event handling across two different components, and render two completely different UIs: a slideshow component, and an ImageList component. That's the power and the ease of custom hooks.

Got You, With Custom Hooks

The main advantage of custom hooks over render props is that there are no nesting. We can call custom hooks at the top, in sequence. However, they still have some issues. With render props, I could conditionally render the component that the render prop was a part of, but because it's against the rules now to conditionally render custom hooks, I can no longer do that. Even if the data for a hook is not needed because of the value of the prop, we still must call the hook. We can't conditionally call the hook, but we can pass values to the custom hook so it could decide to do nothing with those values. We still can get to where we want to get to but it's a little bit more challenging to render props.

Custom hooks don't render markup. I didn't show this as we discussed the render props and the HOCs, but an HOC can render markup before or after or even around the component that we pass to it. A render prop component can do the exact same to the function prop that's passed to it. Custom hooks cannot do that. Basically, for the most part, custom hooks, they deal purely in data. This is why custom hooks actually haven't killed render props. Render props may have basically killed HOCs, but there's still a viable place for render props in the React ecosystem, particularly when you need something to abstract state, logic, and UI. A little bit of UI, shell UI.

Resources

These are several blog posts that you can read at your leisure that cover the history. It's fun to watch the timeline of the approved method of solving this problem, starting in 2015 now to 2019, 2020. React is continuously evolving and we're getting better patterns.

I periodically host a series of short, 3-hour remote React workshops. I call them minishops, because they're short. One of them is called, "Sharing React Component Logic." If you're interested in hands-on learning of when to use different patterns for sharing, including render props, custom hooks, and the like, you should check it out. There's other minishops there as well, so check all of those out. You can just go to my website, benmvp.com. Go on the minishops tab, and you'll see all of them listed there. You can go to my Twitter page @benmvp, and of course this bitly link, bit.ly/qcon-hooks.

 

See more presentations with transcripts

 

Recorded at:

Apr 02, 2021

BT