Redux with React Hooks

Learn how to read data from the store and dispatch actions to write data to the store with the help of Hooks.

With React-Redux v7.1.0, Hooks have officially landed in the official React bindings for Redux. Hooks increase the usability of Redux in React manyfold. While creating a store is much the same, the connect() HOC can be avoided completely. Each method of access (reading or writing by dispatching actions) can be achieved by Hooks.

The most important Hooks to remember are useSelector and useDispatch, which can be loosely compared to mapStateToProps and mapDispatchToProps. Following this analogy, the useSelector Hook is used to read data from the store while useDispatch is used to dispatch actions to write data to the store. React Redux offers a third Hook, useStore, which is not really used in the wild. Its usage should be more of a last resort should you really need access to the store object.

These Hooks can be imported as named imports from react-redux:

import { useSelector, useDispatch, useStore } from 'react-redux';

As is the case with other Hooks, Redux Hooks can only be used in Function components. If you prefer using Class components, you can keep using the connect() HOC. Let’s look at these Hooks one by one.

useSelector(selectorFn, equalityFn)

The useSelector Hook enables us to read values from the store. It expects a so-called selector function as its first parameter. This function receives the complete Redux store and then returns a simple or calculated value (or even a whole tree) of data:

import React from 'react';
import { useSelector } from 'react-redux';

const TodoList = () => {
  const openTodos = useSelector((state) =>
    state.todos.filter((todo) => todo.completed !== true)
  );
  const completedTodos = useSelector((state) =>
    state.todos.filter((todo) => todo.completed === true)
  );
  const allTodos = useSelector((state) => state.todos);

  return (
    <div>
      <p>
        {allTodos.length} Todos. {completedTodos.length} complete and{' '}
        {openTodos.length} open.
      </p>
    </div>
  );
};

The selector function can be extracted if you wish to increase reuse and structure:

import { useSelector } from 'react-redux';

const selectOpenTodos = (state) =>
  state.todos.filter((todo) => todo.completed !== true);

const selectCompletedTodos = (state) =>
  state.todos.filter((todo) => todo.completed === true);

const selectAllTodos = (state) => state.todos;

const TodoList = () => {
  const openTodos = useSelector(selectOpenTodos);
  const completedTodos = useSelector(selectCompletedTodos);
  const allTodos = useSelector(selectAllTodos);

  return (
    <div>
      <p>
        {allTodos.length} Todos. {completedTodos.length} complete and{' '}
        {openTodos.length} open.
      </p>
    </div>
  );
};

Whenever a component is rendered, the selector function is called. It may return a temporary value if the selector function has already been called and the value has not changed since. To determine whether this is the case, Redux uses a Strict Reference Equality Check (===) to check whether the current render has the same reference as the one before.

If an action has been dispatched, useSelector will trigger a re-render of the component if the value is not strictly the same as before. Compared to the connect() HOC, we might encounter more re-renders. For example, selector functions will also be called if the component has rerendered without receiving new props. If you encounter this issue while using the useSelector Hook, you have several options:

  • The component can be wrapped by React.memo. This will avoid unnecessary re-renders of components in which the props did not change.
  • The useSelector Hook can be configured to use a shallowEqual comparison (==) instead and avoids testing for referential equality (===). shallowEqual can be imported from react-redux and be passed to the Hook as a second parameter just like this: useSelector(selectorFn, shallowEqual).
  • Reselect can be useful to use instead as Reselect will always return the same values for as long as nothing has changed in the state.

useDispatch()

The useDispatch() Hook will return a reference to the dispatch function of the store. It can be used to dispatch actions in a similar fashion to the connect() HOC using mapDispatchToProps:

import React from 'react';
import { useDispatch } from 'react-redux';

const addTodoAction = (text) => ({
  type: 'ADD_TODO',
  payload: { text },
});

const TodoApp = () => {
  const dispatch = useDispatch();
  
  const addTodo = () => dispatch(addTodoAction('A new todo element'));
  return <button onClick={addTodo}>Add todo</button>;
};

The action is triggered by the call of dispatch(). However, it does not need to be passed via mapDispatchToProps to arrive in the component, as was the case in the connect() HOC.

Get hands-on with 1400+ tech skills courses.