Redux vs. Context vs. State
By: Denny Scott
Tags: Redux, Web Development, React, Programming, JavaScript

React-Redux Connect/useSelector

To get a better idea of this system, let’s dive into some of the innards of Redux and how React-Redux uses them.

What is Redux?

Redux is a state container for JavaScript applications. Although it was initially built for use with React, it’s not tied to React. Instead, we use a library from the same team called React-Redux to connect Redux to React.

The actual structure and shape of a Redux store are entirely up to the user. It must fit the same shape as the original passed in state, which they refer to as Preloaded State. Traditionally, this is an object and is placed into the variable currentState . At this point, nothing complex is happening, and we just have a single variable that stores our data. Now we want to turn this into a pub-sub.

The PubSub pattern is a form of an observer pattern where users can “publish” updates to a central system, and any subscribers listening to those events are updated accordingly.

So what do these steps look like in our system? Let’s look at the events in sequential order:

An event takes place in the system, which dispatches an action to the store. The store takes that action and updates the store through state reduction. Once the store has updated, we inform all listeners to the store that the data has changed.

Note: If any new subscriptions are added after one has occurred, they will not be notified of a change within the system while iterating through the listeners. After the update is complete at #3, we then move any new subscriptions into our currentSubscriptions .

So, who are the listeners? In the case of Redux, they are any entity that has subscribed to it. You may have seen the syntax for it before, but it happens after you create the store:

Middleware is built using this API, as well as React-Redux. Again, it’s not React specific — absolutely anything can tie into this pub-sub and know about state changes.

One thing you might notice here is that absolutely every single change to the store will call our subscribed event. Does this happen under the hood of our React components? Wouldn’t that be heavy, as each component will be updated no matter the change? Well, this is an excellent opportunity to dive into React-Redux!

What is React-Redux?

React-Redux is a binding library used to connect React to Redux. That’s not all it does though; it also gives us several enhancements under the hood that are often overlooked. Before we look at these enhancements, let’s see how it connects the Redux pub-sub to our components. This can be done in one of two ways: with the traditional Connect syntax or with the newer useSelector and useDispatch hooks.

First, let’s talk about their similarities. Both of these approaches use the React Context system to pass the store instance to any child components in the react tree. Note that this isn’t the store state itself — this is collected later.

Beyond that, there are a couple of misconceptions about how Context is used in React-Redux. We saw earlier that the Context system does have a mechanism to inform child components that our values have updated. But React-Redux does not use this mechanism. Instead, they subscribe directly to the store itself and use the store pub-sub to be notified of possible state changes. This is the store.subscribe() that we mentioned earlier.

Note: This is the useSelector example. The subscription logic for connect is very similar, and can be found here.

When the store state changes, it will call checkForUpdates, which determines if the state alteration has any effect on this component. If it does, it calls forceRender, re-rendering this component. In this entire flow, the Context is never used. So what’s the purpose of it?

A special thanks to Mark Erikson, who helped me walk through the React-Redux/Context approach. He wrote a fantastic post here that goes more in-depth on these reasons.

The purpose of Context within this system is to do exactly as we said above. We use the Context provider to tell React where this store is housed in the hierarchy of the React tree. Any child components of that provider have access to that store instance. This gives us the ability to have multiple stores, nested stores, and a cleaner approach to passing the store instance.

Why doesn’t React-Redux use Context to update the child components? There’s a Github Issue outlining the reasons, but I want to look at a purpose that we’ve already described in this post. Remember the context example we used before? We were able to reduce the amount of re-renders in our system to two. They were:

Re-render the component house the provider, to update the value Re-render any components housing a consumer.

LevelTwo (the component with nether provider nor consumer) was not re-rendered because it was memoized and had no changed props that would need to update the component.

That means, for any children of the provider, and any children of the consumers, your components should be memoized so as to reduce unnecessary re-renders — particularly children that do not require props from said state updates.

Now, I’m not arguing against memoization. Memoization is a crucial tool in your belt, that is under-utilized and often misunderstood (perhaps an article on that next). But that’s a pretty hefty requirement to your components and any future components.

There’s also one other issue we come across that I left purposely vague in our list above.

Render any components that house a consumer.

In the example above, we only have a single consumer, and therefore don’t see the full extent of what this means. Let’s build a case with two components with consumers instead.

So, we’ve updated our context to have two consumers, and both of those components are being displayed at a sibling level in LevelTwo . Now, when you click either of the buttons to update one of their states, you should see this:

No matter what, both of these components are re-rendered, despite them both being memoized, and at a sibling level. That’s because context only notifies their consumers that a change has occurred. It does not know whether that change affects the component using that consumer.

As our application grows, this problem also grows. There are strategies around this, which we will get more into later, but this is why React-Redux does not use context to update its components. Instead, React-Redux relies on its internal pub-sub to notify children.

So how do the re-renders look when we use React-Redux? Let’s start with a code example, then walk through a diagram to explain the steps.

This example will still be using useSelector , but subscription-style is similar to connect .

Here’s our code now using a Redux store, connected through the React-Redux provider and Hooks. Note that there is some additional boilerplate, like the reducer and actions, that isn’t present here. You can check out the code example above to see more.

What happens now when we click the Change context button? Our output should look like this:

Each component was called once for the initial render. Now, when we update our Level Three component, it only re-renders that single component. Not even our provider component updates.

And you will see that if I split our tree into two LevelThree components, like our last example, only a single one would update (assuming they were different state values). How is that?

Partly because of the Redux subscriptions. But you might recall from the Redux breakdown above, a subscription gets called for every single change in the store. And that does happen here, but React-Redux gives us more under-the-hood tools to help increase performance. Here are the steps it takes:

A state update is published. All the listeners, in this case, every single rendered component using useSelector, will call their checkForUpdates function. Each component takes the store instance passed to them from the context earlier, to get the current state of the store. Use the selector passed in to derive a new state value for this component. If the new derived state used by this component has changed from before, re-render the component. If not, do nothing.

This flow can be seen here:

Note: That equality check is where connect and useSelector start to have some differences. We’ll dive into those in a moment. At a high level though, these work similarly.

An even more simplified version is broken down in this diagram:

Note, any child of component three will also still be re-rendered, so, if that’s not desirable, it’s still on the developer to ensure that child components are properly memoized.

Differences between mapStateToProps and useSelector

Although the general idea of useSelector and mapStateToProps are the same, there are some key differences under the hood that can throw you for a loop if you’re not ready for them. However, in my experience these differences, or at least their significance to us developers using these tools, have been greatly exaggerated

To help break down the differences we first need a high-level understanding of what the structure of each approach is. Once we know the difference in architecture, the difference in style should be clearer. So first, let’s examine each signature.

Note: we are going to be doing some selectors that I wouldn’t typically build. This is to try and examine the differences between styles.

Above is the Hook style, using the useSelector Hook of react-redux. Now let’s take a look at the classic mapStateToProps .

As we can see above, the Hook can tie directly into the data and pull out what’s needed. On the other hand, our HOC requires us to create a mapStateToProps function and pass it into our HOC, along with the eventual component. That component will then receive all state data as props.

The DOM output is the same, but there is a difference in the structure of our React document.

ComponentWithHOC React Structure

It appears that our Hook component resembles precisely the elements as they appear in our code. The connect HOC component is a little bit more complicated. Now, we know connect is a HOC, and if you’ve previously used HOC’s, you should have some idea how this looks, but let’s diagram it.

Now we have this extra component that wraps our component. This is responsible for collecting the state data and passing it to our component as a prop. Now, because our component is wrapped with this higher-level component, some extra optimizations are performed out-of-the-box, which aren’t available for Hooks. Namely, we can render both the component creation as well as memoize our actual connect HOC.

What does that mean for people using or migrating to Hooks? It means that where you previously had “free” memoization caused by the connect HOC you will now have to handle on your own. For most cases, that’s as simple as wrapping your component in a React.memo .

Note: There are some interesting differences with mapDispatchToProps, which you may want to check out more about in my article understanding a useEffects dependency array.