useKentCDodds: Hooks for Advanced React Component Patterns

Kent’s excellent Advanced React Component course on egghead.io explains how to create reusable components and properly manage internal state and expose it as an API to app components for reuse across projects. Note: this article will require some prior knowledge of hooks which I won’t fully explain here. I recommend you go through React’s documentation on it (linked in the next paragraph). I also recommend going through Kent’s course if possible since I probably won’t explain the concepts very much in the article either. The code for Kent’s examples can be found here: https://codesandbox.io/s/github/kentcdodds/advanced-react-patterns-v2/tree/egghead/ and my version is here: https://codesandbox.io/s/o7mxmm0n2y where I have updated exercises-final.

React’s new Hooks provide an arguably simpler API for managing state in components. This is perfectly applicable to advanced patterns that require internal state management and thus to Kent’s course. I’ve updated a couple of his examples to use hooks. This mainly involves changing class-based components that require state (this.state) or lifecycle hooks (componentDidMount, etc.).

Managing State with useState

In the following examples, Kent’s original code will be the first gist block. My updates will be the second. Let’s take a look at one of the original toggle examples from the final lesson about how to create a HOC:

In the first example, the static Consumer allows us to use Toggle.Consumer for our higher order component later on. We pass on the current state of the toggle component to the provider and spread any additionally provided props which will at least include onToggle.

It’s not fully clear to me why Kent added this.toggle to the state. I believe this is so it can be passed as one value to the Provider value. This isn’t quite possible with the hooks example, but since we don’t end up updating that property we can create a new object for the value and pass that instead.

In the second example, we use useState in order to generate a state object that will track component changes. The argument to useState is the initial statesimilar to state = { on: false }. useState returns two arguments: the current state value (updated for each render) and a function for updating the state. This function is called by the toggle property on the value passed to the provider. This works the same as the class-based example.

setToggleState, i.e. the function that useState returns to update the state is similar to setState for class-based components. We can pass the new state to it (beware! Original state properties are not merged in with the passed value). As far as I can tell, you can’t pass a callback function as the second argument to the update function, but you can pass a callback as the only argument which takes a parameter that is the previous state. We can use this to change the next value based on the previous value as well as call the onToggle prop passed in with this next value.

Managing Context too!

In addition to updating the use of class state with useState, I noticed that there is also a useContext provider. I wasn’t fully clear on what this did, and it doesn’t look like there are a lot of examples out there yet, but from what I can tell it gives you the value of a context consumer on each render update. Looking at an example may help make that clearer:

In the first example we consume the context as usual using Consumer. Consumers take a function as their child which takes a parameter that is the current context.

useContext also returns the current context. In the second example, we can get toggleContext using useContext rather than having to wrap the consumer component in ToggleContext.Consumer. Everything else about the HOC can remain the same.

In my opinion, the hooks versions of the components in this case are a bit nicer, cleaner, and easier to reason about (once you have grasped hooks). This is particularly true of the use of a context consumer. One thing that I would like is the ability to use the older syntax of new state + callback that we had with setState such as:

setToggleState({ on: !toggleState.on }, ({ on }) =>
props.onToggle(on)

It’s also great to point out that even after these updates none of the existing unit tests broke. Hooks can be conveniently tested in at least practically the same way as their class component counterparts before them.