One of the GOOD things about React is that there is a lot of freedom when it comes to solving your structural and architectural problems.

One of the BAD things about React is that there is a lot of freedom when it comes to solving your structural and architectural problems.

Why is this a bad thing? It’s only bad because it means that there are a lot of questions that have to be answered early on in the life of a React app and then the resulting patterns have to be followed thoroughly.

Questions like:

Does this application need Redux? If so, how much state should Redux be responsible for? How smart should the smart components be? How dumb should the dumb components be? What components should be responsible for affecting and consuming global state?

If these questions are not asked and proper patterns are not determined, then the application will suffer. The application will be inconsistent and buggy. And those bugs will be more difficult to track down.

The first application I built with React was a travesty. There’s no polite way of putting it. There was hardly any separation of responsibilities between the components at all. All components could affect and consume global state. All components were styled ( there weren’t any designated ‘presentational’ components). There wasn’t much (although, there was some) difference between smart and dumb components.

It was just a mess. I really didn’t know what I was doing. I learned a few things though. I learned the importance of asking the right questions early on and revisiting those questions often. In the process, I have found there are several different options when it comes to state management patterns. I’ll try to go through several of them and point out some pros and cons as well as show how React v16.3 has opened up the way for a new pattern.

Before I get started, let’s go over some definitions.

State Definitions

There are 4 different kinds of state in a React application:

Global State: State that is maintained outside of the component tree. Typically this is Redux. The state that is held here is accessible from anywhere in the application.

Component State: State that is held within a component and manipulated with this.setState .

Relative State: State that is passed from parent to child down the component tree.

Provided State: The state is placed into a context ( using React’s Context API ) by a provider. It can then be consumed by components individually without the need to be passed down the component tree.

I’ll go into more detail on each of these as we go along. It’s important to note that none of these types of state are ‘better’ or ‘worse’ than any other. They each serve their own purpose and provide their own solution to a problem. The best state management pattern, in my opinion, will use all four of these state types, together, in some way or another.

Component Definitions

Container: When I say container, I’m referring to a ‘smart-component’. These components have more responsibility than the dumb-components. How much responsibility will depend on the state management pattern, but typically, these components are responsible for affecting and consuming global state and passing state to dumb-components.

Components: These are the dumb-components. Again, their responsibility changes based on the pattern they are in, but typically, they are only concerned with presentation and styling. They usually don’t create their own state and consume it from elsewhere.

Pattern 1: Prop — drilling

This pattern is probably the most common and is the pattern that I have most often used in the apps that I have built.

The idea is that the containers ( smart-components ) are solely responsible for creating state; either component state or global state. They are also solely responsible for consuming state from the global store. They can create their own state by using this.setState and then pass that state down the component tree as props .

When the global state needs to be changed, that change is initiated from the container by dispatching an action ( if you’re using Redux ).

This pattern has three of the four types of state. Global state, component state, and relative state ( remember, we really want to be using all four types ).

The pros of this pattern is that the responsibilities are well defined. We know that if the the global state needs to be changed, that will happen from a container. We know that if a component needs to receive state from somewhere, it will receive it from a container.

One big con of this pattern, however, happens with the relative state. When a container passes props to a component and then that component in turn passes them down to another component, this is known as prop-drilling. It’s OK for the first one or two layers of the component tree, but when that component tree becomes three or four or five layers deep, stuff can get really messy.

This, then, is the reasoning behind the next pattern.

Pattern 2: Redux — centric

This pattern is typically used in an attempt to eliminate the problems caused by the prop-drilling pattern.

The idea here is similar to the first pattern. The containers have the same responsibilities as before but the main difference is that our dumb components are a little bit smarter than before.

What I mean by that is, they now are allowed to have direct access to the global state instead of relying on the container to pass state to them. Instead of the containers creating the state and putting it into their state object, they are placing their state into the hands of the global state.

While this does eliminate prop-drilling, it introduces a lot of other problems.

Firstly, there is no longer a clear definition of responsibilities. In the prop-drilling pattern we knew what each component was responsible for when it came to state management. But now that dumb components can access global state on their own, that responsibility is blurred.

Secondly, it’s still possible for a dumb component to receive relative state. Which means that we have to answer another question:

At what point should relative state be moved to global state?

What I mean by that is, if a component receives relative state from a container ( 1 layer of prop-drilling), it seems like over kill for that state to become global state. But if that component is passing props down to another component ( 2 layers of prop-drilling ), now we might consider moving that state into relative state, but it still might be over kill. You can see how this line can easily become blurred.

Also, this can quickly become disorganized when a component is receiving relative state as well as global state at the same time.

Thirdly, global state does not follow the life cycle of a React component. With the relative state pattern, when a container un-mounts, it’s state is reset. Meaning, a component that is receiving state from a container when it un-mounts will no longer have those props.

If, however, that component is pulling its state from global state and then it un-mounts, that state still exists in global state. As soon as that component remounts, it will receive the exact same state from global state. That is often times a bug. The component is now tightly coupled that part of the global state, reducing its re-usability.

One way of combating this is to reset parts of the global state when a component un-mounts. While this works, it’s a good indication that something is wrong. If global state has to be reset when a component un-mounts, it probably should have been in the component state to begin with.

This pattern makes use of three of the four state types as well, just like the prop-drilling pattern. However, it does so in a way that can easily become buggy and disorganized.

Pattern 3: Stateless Components

Here’s a diagram of what this pattern looks like:

Ok, just kidding. But for real, I didn’t draw a diagram for this pattern because there aren’t enough red arrows in the universe. Remember my first React application I mentioned? This is pretty close to it.

This pattern is essentially the Redux-centric pattern on steroids. Instead of the containers being class components with their own state object, they are a functional components and they derive all of their state from global state.

Obviously, since none of these components have their own state object, component state is completely eliminated, and relative state goes out of the window for the most part as well.

Your reducers and action definitions ( if you are using Redux ) become extremely bloated and difficult to manage. And remember the way we were ‘resetting’ global state in the Redux-centric pattern? Well we can’t do that here because we can’t attach any events to the componentWillUnmount life-cycle hook method because all of our components are functions.

This pattern leaves us with only one of the four types of state. Global state.

Pattern 4: Provided State

With the release of React v16.3, the Context API is now officially supported. I’m sure you’ve heard about it at this point because everyone is talking about it. Why are they talking about it? Well, quite simply, it’s the missing piece in all of the patterns described above.

If you haven’t heard of the Context API, check out this article I wrote on it.

Each context has a provider and a consumer . In the diagram above, the Containers are the providers for the context, and the Components are the consumers of the context. Containers will derive state from the global state as well as create it themselves and then ‘provide’ that state to the context. The components can then pick out whatever props they need directly from the context instead of having to rely on the container or global state to pass them down.

The separation of responsibilities in this pattern are similar to pattern 1 in that the containers are the only components that can manipulate and consume the global state. The components are only concerned with grabbing props from the context and displaying it.

How does this solve our problems from the earlier patterns?

Firstly, because a component can grab whatever it needs directly from the context, there is no need to pass anything from the container to the components. While there may still need to be some need for relative state between components, our prop-drilling problem is completely eliminated.

Secondly, we no longer have to be concerned with where a component sits in the component tree. In the first pattern, once a component was placed in the component tree and props were passed to it, it could not be moved without refactoring where the properties were coming from. With context, the position of the component in the tree is irrelevant making the components far less coupled and far more reusable.

Thirdly, remember the problem we had with having to reset parts of the global state when a component un-mounts? When it comes to context, each context’s state is tied to the life-cycle of the provider . When the container that is providing state to the context un-mounts, all of that state is automatically reset, thus making the component less coupled and more reusable.

This is the first pattern we’ve discussed that utilizes all four of the types of state.

There are more benefits to using context than this short list but I hope that this is enough to convince you of why it is the better option when it comes to state management.

Note: It is quite possible, in this pattern, to completely replace Redux with React’s new Context API. In fact, Redux is written on top of the Context API to begin with. All you need to do is add actions and dispatchers to update the provided state and you would have your own version of Redux. Here’s a short article on the subject. https://medium.freecodecamp.org/replacing-redux-with-the-new-react-context-api-8f5d01a00e8c

I hope this has been helpful to you! Please leave your comments and claps below.