Redux and alternatives - TwoGears/hakomo-guides GitHub Wiki

Why we need a state management tool:

In typical React, the way to handle data between disconnected components is through prop drilling. Since there is no global state that components can access if, for instance, you want to pass data from a top-level component to a fifth-level component, you’ll have to pass the data as a prop on each level of the tree until you get to your desired component.

This results in writing a ton of extra code, and giving components properties that they will never use also affects their architectural design. In order to solve this problem, we needed a way to provide a global state that all components, no matter how deeply nested they are, could access.

By solving this, Redux, an open-source JavaScript library for managing application state, became the go-to solution for React developers.

Do You Need Redux:

Link Link2

How Redux works:

Redux is used to manage the state of a React app in a centralized place. “State” simply refers to data you need to render the user interface correctly.

It consists of four main building blocks:

  1. A single, centralized state (i.e. a global JS object you could say) which is not directly accessible or mutable.
  2. Reducer functions that contain the logic to change and update the global state (by returning a new copy of the old state with all the required changes).
  3. Actions that can be dispatched to trigger a reducer function to run
  4. Subscriptions to get data out of the global state (e.g. to use it in React components)

Redux working structure

When to use Redux

You should feel free to use Redux, but when you do, you should have a reason for it. Redux is useful if your component:

  • Uses I/O like network or device APIs.
  • Saves or loads state.
  • Shares its state with non-child components.
  • Deals with any business logic or data processing shared with other parts of the application.

Even though Redux solves our state management problem, it is really time-consuming to use, has a difficult learning curve, and introduces a whole new layer of complexity to our application.

Fortunately, the React Context API solves this problem. When combined with React Hooks, we have a state management solution that is less time-consuming to set up, has an easier learning curve, and requires minimal code.

The React Context API

The new Context API came with React 16.3. Here’s how Context is explained in the React documentation:

Context provides a way to pass data through the component tree without having to pass props down manually at every level.

The React Context API is React’s way of managing state in multiple components that are not directly connected.

It generally consists of three building blocks:

  • A Context Object
  • A Context Provider
  • A Context Consumer

The Context Object

Create Context

You can define the Context object in a separate file or right next to a component in a component file. You can also have multiple Context objects in one and the same app.

But what IS Context actually?

In the end, it’s just some data - e.g. a JavaScript object (could also be just a number or string etc) which is shared across component boundaries. You can store any data you want in Context. For this, it’s not enough to create a Context object though, you also need to provide it!

Providing Context

With the Context created, you can now provide it to all components that should be able to interact with it (i.e. read data from it or trigger a method stored in Context - more on that later).

Context should be provided in a component that wraps all child components that eventually need access to the Context.

For data that should be available in your entire app, you have to provide Context in your root component (e.g. ) therefore. If you only need Context in a part of your app, you can provide it on a component a little further down the component tree.

You do provide Context like this:

Context Provider

Please note the value prop on <ShopContext.Provider>: The value you set here is forwarded to the wrapped child components. And if the value changes, it will also change in the child components.

The fact that updates to the data passed to value are received by consumers of our Context and allow us to use the Context API as a global state management tool.

Consuming Context

For that, let’s first of all have a look at how other components can consume context. We got two options:

Using Context.Consumer

Context Consumer

<ShopContext.Consumer> is a wrapper component we can use to “inject” the Context provided in some parent component (doesn’t have to be the immediate parent) in this child component. The context is received as an argument to a function which you pass as a direct child to <ShopContext.Consumer>. That’s important! You don’t (directly) place JSX between <ShopContext.Consumer> ... </ShopContext.Consumer. The context object is the exact same object passed to value on our <ShopContext.Provider> - this means that when the data passed to value changes, the context object in the child component also changes and hence this child component updates. That’s a nice form of updating different components based on some centralized state.

Using static contextType

Besides using <ShopContext.Consumer>, we can also get access to our Context by setting a static property in our (class-based) child component. Important: Unlike <ShopContext.Consumer>, which you can use in functional and class-based components, this method will only work in class-based (“stateful”) components!

Side note: With React Hooks you also got a way of tapping into Context anywhere in functional components, too.

Using Static Context Type

React gives you a this.context property in your class-based components if you set static contextType = YourContext in the component. The big advantage is, that - unlike as with the previous approach - you can now use the Context object anywhere in your component (including places like componentDidMount).

Without Hooks, the Context API might not seem like much when compared to Redux, but combined with the useReducer Hook, we have a solution that finally solves the state management problem.

What are Hooks in React?

Hooks are a type of function that enable the execution of custom code in a base code. In React, Hooks are special functions that allow us to “hook into” its core features.

React Hooks provide an alternative to writing class-based components by allowing us to easily handle state management from functional components.

The useContext Hook

To avoid unnecessary component nesting like wrapping content in a Consumer component and then passing a function as a child just so we could access (consume) our state, we can use useContext hook which makes things a lot nicer and more straightforward. In order to access our state with it, all we need to do is call it with our created context as its argument:

The useContext Hook

The useReducer Hook

Just like the Redux`s reducer, the useReducer Hook receives two values as its argument — in this case, A reducer function (which in turn receives the state and an action as arguments) and an initial state.

The useReducer Hook

The useReducer Hook plus the Context API

Setting up our store

Now that we know how the Context API and the useReducer Hook work individually, let’s see what happens when we combine them in order to get the ideal global state management solution for our application. We’ll create our global state in a store.js file:

Setting up store ( Context )

Accessing our state globally

In order to access our state globally, we’ll need to wrap our root  component in our StoreProvider before rendering it in our ReactDOM.render() function:

Accessing state ( Context )

Now, our store context can be accessed from any component in the component tree. To do this, we’ll import the useContext Hook from react and the store from our ./store.js file:

The useContext Hook

Adding and removing data from our state

We’ve seen how we can access our global state. In order to add and remove data from our state, we’ll need the dispatch method from our store context. We only need to call the dispatch method and pass in an object with type (the action description as defined in our StateProvider component) as its parameter:

Adding and removing data from store ( Context )

Conclusion

To a good extent, Redux works for state management in React applications and has a few advantages, but its verbosity makes it really difficult to pick up, and the ton of extra code needed to get it working in our application introduces a lot of unnecessary complexity.

On the other hand, with the useContext API and React Hooks, there is no need to install external libraries or add a bunch of files and folders in order to get our app working. This makes it a much simpler, more straightforward way to handle global state management in React applications.

But there is an important gotcha!

The Context API (currently) is not built for high-frequency updates (quote of Sebastian Markbage, React Team), it’s not optimized for that. The react-redux people ran into this problem when they tried to switch to React Context internally in their package.

My personal summary is that new context is ready to be used for low frequency unlikely updates (like locale/theme). It’s also good to use it in the same way as old context was used. I.e. for static values and then propagate updates through subscriptions. It’s not ready to be used as a replacement for all Flux-like state propagation.  --- Sebastian Markbage

So for the moment, it seems like you might want to look into using React Context for low-frequency updates (e.g. theme changes, user authentication) but not use it for the general state management of your application.

Resources: Use Hooks + Context, not React + Redux; Do React Hooks Replace Redux; Redux vs React’s Context API;

⚠️ **GitHub.com Fallback** ⚠️