Skip to main content

[Redux] Hooks


Since version 7.1, hooks functions have been provided, eliminating the need to rely on connect(). This makes it easier and clearer to access state and dispatch actions to change the state in Redux.


Process

  1. User triggers an action.
  2. The action goes through middleware to check if any additional events need to be triggered.
  3. Then, it goes to the Reducer to update the data.
  4. Finally, the state is updated, and the UI reflects the changes.


Preparation

By using createStore, the state is stored in the store object. Then, the store object is passed to the App component through the Provider, allowing it to be accessed by the nested components in the application.

const store = createStore(rootReducer)

ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)

useSelector & useDispatch

  • useSelector : access the state stored in the Redux store.
  • useDispatch : dispatch action to redux store。

useSelector

It allows us to directly extract data from the state in the Redux store into components.
import React from "react";
import { useSelector } from "react-redux";

export const CounterComponent = () => {
const counter = useSelector((state) => state.counter);

return <div>{counter}</div>;
};
info
  1. useSelector subscribes to the Redux store, and when an action is dispatched, it triggers the selector function.
  2. You can use useSelector multiple times within a component. However, each call to useSelector() creates a separate subscription to the Redux store. With the batching update behavior in React (used by react-redux v7), multiple calls to useSelector within the same component will only trigger one re-render for the returned values. This prevents unnecessary re-renders of all components within that component, even if useSelector is called multiple times.

useSelector controls when rendering occur

In component A, useSelector() is used to retrieve the value of count from the Redux store. When an action is dispatched to the Redux store, it uses the equality function (equalityFn) to strictly determine whether the count has changed. If it has changed, the component A will be forced to re-render, and then it will obtain the updated value of count again.


useDispatch

Returning a dispatch function enables you to directly trigger the Reducer by calling it through this method.

Example
import React from "react";
import { useDispatch } from "react-redux";

export const CounterComponent = ({ value }) => {
const dispatch = useDispatch();

return (
<div>
<span>{value}</span>
<button onClick={() => dispatch({ type: "increment-counter" })}>
Increment counter
</button>
</div>
);
};

Passing from the parent component to the child component.
  • It is recommended to use useCallback to memoize the function before passing it as a prop to the child component. This way, the child component will receive a memoized version of the function, and unnecessary re-renders can be avoided.
import React, { useCallback } from "react";
import { useDispatch } from "react-redux";

export const CounterComponent = ({ value }) => {
const dispatch = useDispatch();
const incrementCounter = useCallback(
() => dispatch({ type: "increment-counter" }),
[dispatch],
);

return (
<div>
<span>{value}</span>
<MyIncrementButton onIncrement={incrementCounter} />
</div>
);
};

export const MyIncrementButton = React.memo(({ onIncrement }) => (
<button onClick={onIncrement}>Increment counter</button>
));

Dispatching actions directly within a component.
  • Using the dispatch method is very stable.
  • We pass dispatch as one of its dependencies to our useEffect function because it won't change. By passing dispatch, it prevents infinite loops and ensures that this function is called only once.
./store/todos/actionCreators
export const fetchTodos = () => async () => {
dispatch(setLoading({ status: true, tip: "加載中..." }));

const response = await fetch("/fetchTodos", () => dbTodos);

dispatch({ type: SET_TODOS, payload: response });

dispatch(setLoading({ status: false, tip: "" }));
};

TodoApp.js

import { useDispatch } from 'react-redux'
import {fetchTodos} from './store/todos/actionCreators'

const TodoApp = () => {
const dispatch = useDispatch()

useEffect(() => {
dispatch(fetchTodos())
}, [dispatch])

...
}


useStore

This hook returns all the store objects of the redux <Provider> component.

import React from "react";
import { useStore } from "react-redux";

export const CounterComponent = ({ value }) => {
const store = useStore();

// This is just an example! Don't do this in projects.
// Using useStore allows direct access to the entire Redux store.
// However, this approach is not recommended because it violates one of the core principles of Redux - the Single Source of Truth.
// It may lead to performance issues and unnecessary re-renders.
// Instead, it is advisable to avoid direct access to the entire Redux store in components and use connect or useSelector hook to select the required state slices.
// This helps maintain predictability and maintainability in the application.

return <div>{store.getState()}</div>;
};

Here are the resources, you can play with them yourself:

Codesandbox 💪

https://codesandbox.io/s/infallible-silence-10lmx?file=/src/Main.js


References