Our Product Design Process book is out now!

ORDER NOW

Demystifying React Hooks vs Redux

Redux and React hooks should not be seen as the opposite of each other. They are different things, with distinct goals.

When React Hooks was born, several developers seemed confused by the concepts introduced and how they will interpolate into the Redux library. The useReducer hook increased this confusion.

In this article, I will explain how Redux and the new React Hook useReducer are different, and how they can be used.

Redux and React Hooks: State Management

Redux was created to manage state across the application. It provides several tools to decrease the complexity of handling the global application state.

The new additions to the React library, useContext and useReducer, also came to manage the state of a React application. However, it has a subtle difference from Redux, which I'll clarify further below.

But first, let me introduce you to React Hooks.

What is React Hooks?

React Hooks is the new way of handling state and life cycle into React components, without relying on component classes. It was introduced in the 16.8 version of the library and has the intention to decrease the complexity of the components, by sharing logic between them.

React Hooks provides an easy way of handling the component behavior and share the component logic.

The React Hooks feature doesn't have the intention of replacing the previous knowledge of React concepts like lifecycle, state, props, context, and refs.

With React Hooks, we can write our custom hooks to extract logic that will be used in more than one component and implement the D.R.Y (Don't Repeat Yourself) pattern.

In the snippet below, you can see an example of custom hooks written for one of our projects:

import { useRef } from 'react';
import { AppState } from 'react-native';
import { Audio } from 'expo-av';

function useAudio(exit) {
  useEffect(() => {
    AppState.addEventListener('change', handleAppStateChange);
    return AppState.removeEventListener('change', handleAppStateChange);
  }, []);

  useEffect(() => {
    handleStopAudio();
  }, [exit]);

  function handleAppStateChange(nextAppState) { ... }
  
  function handleStopAudio() { ... }

  async function startRecord() { ... }

  async function stopRecord() { ... }

  return [startRecord, stopRecord];
}

export default useAudio;

In this file, we are concentrating the record logic into one hook. That will be used in the component. As shown below:

function AssessmentText({ ..., exit }) {
  const [startRecord, stopRecord] = useAudio(exit);

Okay, now that we know React Hooks, let's take a look at Redux.

What is Redux?

Redux is a library for managing the global application state. In this library, we can find several tools that help us, developers, to be in touch with the state of the application and also transform it by giving the user the ability to emit actions.

Redux, as the documentation says, can be described in three fundamental principles:

  1. Single source of truth: the global state of your application is stored in an object tree within a single store.

  2. The State is read-only: The only way to change the store is by emitting actions.

  3. Changes are made with pure functions: To update the store, the reducer should be written as a pure function.

Redux even updated the library with its custom hooks. These can be used to integrate the components that use the React Hooks features to access data from the store and dispatch actions without relying on the components classes.

Now that we are a little more familiar with Redux and React Hooks let's see the difference between them.

React Hooks vs Redux

Both of them handle state management, but with several differences. There is a lot of abstraction into the following sentence, but this seems like a golden rule to know when you should use Redux into your application:

Redux should be used in applications that have several features. With these features sharing chunks of the same information.

While using the useContext and useReducer, the Redux functionalities can be reproduced. Redux offers some free tools that help us to manage the application without reinventing the wheel. It also offers us the following:

  • Saving the actual state of our entire application;
  • Tools to debug while developing like redux-devtools;
  • Change the actual state without triggering one re-render of the entire application, by using the connect() function.

Let's understand a little more about the actual React addictions, React Hooks, to manage the global state and its ideal use.

The useContext hook

The useContext hook comes to access the state that is shared by the providers. The best-case scenario to use the React Hooks useContext is in a small application or to share small pieces of information across the components, like the theme of the application. The useContext has two principal concepts, and they are listed below.

  1. Provider
    The provider is responsible for managing the state changes. These changes are spread across all the consumers. A great example to use the useContext hook is theming.
import React, { useState } from 'react';

// creating the context to share the data across components
export const ThemeContext = React.createContext();
export const PACIENT = 'patient'
export const RESEARCHER = 'researcher'

const ThemeProvider = ({ children }) => {
  const themes = {
    patient: {
        bgColor: '#ffff',
        fontColor: '#434A60'
    },
    researcher: {
        bgColor: '#59B2CA',
        fontColor: '#ffff'
    },
  };

  // setting the logic to handle the theme changes
  const [theme, setTheme] = useState(PACIENT);

  return (
    <ThemeContext.Provider
      value={{
        theme: themes[theme],
        setTheme,
      }}
    >
      {children}
    </ThemeContext.Provider>
  );
};

export default ThemeProvider;

This provider creates the theme object that will be used in all applications and one function to update it, the setTheme. In the snippet below, you can check the setTheme in action.

import { RESEARCHER, PARTICIPANT } from '../constants'

const PathSeletion = ({ navigation }) => {
  const [ setTheme ] = useContext(ThemeContext);
  
  ...
  
  return (
      <>
        <button 
            onClick={() => setTheme(RESEARCHER)}
        >
            {RESEARCHER}
        </button>
        <button 
            onClick={() => setTheme(PARTICIPANT)}
        >
            {PARTICIPANT}
        </button>
    </>
  ) 
}
  1. Consumer
    The Consumer, as the self-explanatory name says, consume the content that it is subscribed for. Every time that one consumer changes the state of the provider these changes are also spread by all other consumers. Let's take a look at the following example:
import React, { useContext } from 'react'

import { ThemeContext } from '../Providers/ThemeProvider';

function Login() {
    // consuming the current theme
    const [ theme ] = useContext(ThemeContext)

    // using them
    const { bgColor, fontColor } = theme
    return (
        <div 
            style={{ 
                background: bgColor,
                ...
            }}
        >
            <h1 style={{ color: fontColor }}>Login</h1>
        </div>
    );
}

export default Login

In the snippet above, we can check the use of the global object theme to set the background color of our application.

After getting to know the useContext function, there is one more that confuses the developers. The useReducer hooks. In the section below, I will be walking through this functionality and explain its use case.

Can useReducer replace Redux?

The useReducer hook should be used in components that have complex logic behind it. It shows as the main confusion with the Redux library, because developers tend to think that useReducer could replace the state manager library. But in fact, its use should be restricted to components. This is mainly because the global use of useReducer demands rewrites of new code to features like the control of components update.

How useReducer works?

Now that we understand the use case for the useReducer hook is time to show some action. In the example below, we are using the new React Hook addiction useReducer to control one component with different states.

Let's take a deep look at the useReducer input and output parameters.

Input Parameters

The declaration of the useReducer hook will most likely look like this:
const [state, dispatch] = useReducer(reducer, INITIAL_STATE);

Where we can identify that the useReducer hook receives two parameters, as shown below:

  1. The reducer function: the one used to transform the global state.
    In our example, the reducer function is the responsible response to three actions:
function reducer(state, action) {
  switch (action.type) {
    case "UPDATE_INPUT":
      return {
        ...state,
        isAddDisabled:
          !state.items.indexOf(state.value) || state.value.length < 5,
        value: action.value
      };

    case "ADD_ITEM":
      if (state.value.length < 5) {
        return state;
      }

      return {
        ...state,
        value: "",
        isAddDisabled: true,
        items: preventDuplicate(state.items, state.value)
      };

    case "REMOVE_ITEM":
      return {
        ...state,
        items: state.items.filter(item => action.item !== item)
      };

    default:
      return state;
  }
}
  1. The initial state: represents the default state.
const INITIAL_STATE = {
  value: "",
  isAddDisabled: true,
  items: []
};

Output Parameters

The first output parameter of the hook is the actual component state. This state should be used in our application to represent the data.

 <input
    type="text"
    value={state.value}
    placeholder="Add new task"
    onChange={event => handleInput(event.target.value)}
  />

It also follows the same rule of the React state, that is:

Never write directly to the state and the state should be updated by emitting actions.

The actions that will be dispatched by our components should be always represented as one object with the type and payload key. Where type stands as the identifier of the dispatched action and the payload is the piece of information that this action will add to the state.

  function handleInput(value) {
    dispatch({ type: "UPDATE_INPUT", payload: value });
  }

Wrapping up

By adding all these pieces together, we can see the final result in the application below:

React Hooks or Redux: both.

Redux and React Hooks should be seen as complements and also as different things. While with the new React Hooks additions, useContext and useReducer, you can manage the global state, in projects with larger complexity you can rely on Redux to help you manage the application data.

If you’re thinking about building an application, both can be used. While Redux holds the global state and actions that can be dispatched, the React Hooks features to handle the local component state.

Found this article useful? You might like these ones too!

At Imaginary Cloud, we simplify complex systems, delivering interfaces that users love. If you’ve enjoyed this article, you will certainly enjoy our newsletter, which may be subscribed below. Take this chance to also check our latest work and, if there is any project that you think we can help with, feel free to reach us. We look forward to hearing from you!