When to use native React.useReducer Hook and how it differentiate from Redux

ReactjsReduxReact Hooks

Reactjs Problem Overview


So, Hooks are available from React 16.8. From their documentation, Hooks come as a replacer of state in functional components. The basic hooks are: useState, useEffect, useContext, but there are also some additional hooks, one of them being useReducer, and it looks like it uses the same action-dispatch architecture as Redux does.

The questions would be if it comes as a replacement of Redux because of the resemblance ?

Does it suits particular projects better ?

Where would it fit ?

Reactjs Solutions


Solution 1 - Reactjs

Redux is a library that encourages data flow in a specific manner.

react-redux on the other hand implements the React friendly approach and provides a lot middlewares and wrappers so that the library consumers do not have to set up the entire process on their own.

While useReducer is a part of how Redux works, it isn't Redux in its entirety. In order for you to use dispatch and state deep down in your components you would still have to use useContext and useReducer in a combination which would be like re-inventing the wheel.

On top of that useReducer just gives you a dispatch method which you can use to dispatch plain old objects as actions. There is no way yet to add middlewares to these such as thunk, saga and many more.

You also can have multiple reducers in your application using useReducer but then the way to combine these to form a single store still have to be managed by the developer.

Also React docs state that useReducer is an alternative to useState when state logic is complex

> useReducer is usually preferable to useState when you have complex > state logic that involves multiple sub-values or when the next state > depends on the previous one. useReducer also lets you optimize > performance for components that trigger deep updates because you can > pass dispatch down instead of callbacks.

What hooks like useContext, useReducer do is that they eliminate the dependency on Redux for small apps.

Solution 2 - Reactjs

So, if Redux and useReducer were to be compared

Redux:

  • centralised state
  • forges more de-coupling
  • has middlewares: Redux thunk and Redux logger
  • actions can only hit one Store
  • maybe more suitable for big projects

useReducer:

  • local state
  • no wrapper component
  • needs useContext in order to reinvent the wheel
  • comes with other native hooks
  • no extra dependencies needed
  • multiple stores maybe(actually reducers that can act as store)
  • maybe more suitable for small projects

Solution 3 - Reactjs

> 1.) Does useReducer come as a replacement of Redux?

Let's clarify what Redux is, so we can compare its main parts to a vanilla React solution with useReducer:

enter image description here (inspired by this article)

How to replace all parts with vanilla React

Action Dispatcher / State Selector

This is a replacement for what React Redux does:

import { useReducer, useContext, useMemo } from "react"
import { rootReducer } from "./reducers"

const GlobalContext = React.createContext()

const Provider = ({ children }) => {
  const [state, dispatch] = useReducer(rootReducer, { count: 0 });
  const store = useMemo(() => [state, dispatch], [state]);

  // You can also separate dispatch and state context providers
  return (
    <GlobalContext.Provider value={store}>{children}</GlobalContext.Provider>
  );
};

// Provider is placed at top-level to emulate a global state store
ReactDOM.render(<Provider> <App /> </Provider>, document.getElementById('root'))

const Comp = () => {
  // extract this UI logic in its own custom hook for better encapsulation
  const [state, dispatch] = useContext(GlobalContext);
  // ...
};
Data Store

Redux store can be instantiated (createStore) and accessed quite flexibly. With vanilla React, the store is bound to a single useReducer in the UI. We can pass its state down via context or props.

Store Reducer

The exact same pure rootReducer function is used for vanilla as with Redux.

Middleware API

redux-thunk and redux-saga are two of the most popular middlewares for async operations and side effects in Redux. With useReducer, we do not have any built-in middleware API. Instead we do the async processing first and afterwards forward the result to dispatch:

const [state, dispatch] = useContext(GlobalContext);
// ...
<button onClick={() => dispatchAsync(dispatch)}> Process </button>

const dispatchAsync = dispatch => {
  fetchData().then(data => dispatch({type: "increment"}, data)) 
};

It is still possible to integrate existing Redux middlewares with useReducer, as long as their common API is covered - you can take a look at this answer for more infos.

Redux DevTools (Time traveling, debug support)

There is no direct integration for useReducer available, so you may miss an important workflow tool here. The reinspect library uses Redux DevTools to also inspect useState and useReducer (haven't tested though).


> 2.) Does it suits particular projects better? Where would it fit?

useReducer usage starts with local state and in the component scope. As you have seen, it can also be lifted to the global scope to take over most of Redux' roles.

In some situations, useReducer can even provide more flexibility, as the global state can be divided between multiple contexts. Example: Separate low priority and high priority state changes in different state trees/contexts.

With vanilla React, you will mainly miss out on Redux DevTools and popular middleware libraries. On the other hand functions like combineReducers can be easily re-implemented for useReducer, as seen here.

General rule of thumb: Start using vanilla React for your app and add functionality like a global Redux store incrementally, as soon as you feel, this becomes mandatory (also depends on app size).


const GlobalContext = React.createContext();

const Provider = ({ children }) => {
  const [state, dispatch] = useReducer(rootReducer, { count: 0 });
  const store = useMemo(() => [state, dispatch], [state]);

  // You can also separate dispatch and state context providers
  return (
    <GlobalContext.Provider value={store}>{children}</GlobalContext.Provider>
  );
};

const rootReducer = (state, action) =>
  action === "increment" ? { count: state.count + 1 } : state;

const dispatchAsync = dispatch => {
  // just do the async operation before and invoke dispatch afterwards
  setTimeout(() => dispatch("increment"), 1000);
};

const Comp = () => {
  // You can extract this UI logic in its own custom hook for better encapsulation
  const [state, dispatch] = useContext(GlobalContext);

  return (
    <div>
      <div>Counter: {state.count}</div>
      <button onClick={() => dispatchAsync(dispatch)}>
        Increment async (1sec delay)
      </button>
    </div>
  );
};

ReactDOM.render(<Provider><Comp /></Provider>, document.getElementById("root"));

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js" integrity="sha256-32Gmw5rBDXyMjg/73FgpukoTZdMrxuYW7tj8adbN8z4=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js" integrity="sha256-bjQ42ac3EN0GqK40pC9gGi/YixvKyZ24qMP/9HiGW7w=" crossorigin="anonymous"></script>
<div id="root"></div>
<script>var { useReducer, useContext, useMemo } = React</script>

Solution 4 - Reactjs

useReducer's state is local to a single component - if you wanted to use this state throughout your app, you'd need to pass it (and/or the dispatch function) down via the props. It's effectively just a more structured version of useState - in fact, useState is implemented using useReducer under the hood!

Redux, on the other hand, does a bit more - among other things, it makes the state available to the entire app via a Context, and then provides APIs to connect your deeply nested components to this state without passing props down.

So in other words:

  • useReducer gives you structured local state updates.
  • Redux gives you structured and centralized state updates.

If you wanted to 'roll your own Redux' with Hooks, you'd need to use some combination of useReducer and useContext.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionRelu MesarosView Question on Stackoverflow
Solution 1 - ReactjsShubham KhatriView Answer on Stackoverflow
Solution 2 - ReactjsRelu MesarosView Answer on Stackoverflow
Solution 3 - Reactjsford04View Answer on Stackoverflow
Solution 4 - ReactjsJoe ClayView Answer on Stackoverflow