惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Recent Commits to openclaw:main
Recent Commits to openclaw:main
L
LangChain Blog
月光博客
月光博客
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
博客园 - 【当耐特】
宝玉的分享
宝玉的分享
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Last Week in AI
Last Week in AI
人人都是产品经理
人人都是产品经理
博客园_首页
T
Tailwind CSS Blog
P
Proofpoint News Feed
雷峰网
雷峰网
D
Darknet – Hacking Tools, Hacker News & Cyber Security
IT之家
IT之家
V
Vulnerabilities – Threatpost
阮一峰的网络日志
阮一峰的网络日志
C
CERT Recently Published Vulnerability Notes
Attack and Defense Labs
Attack and Defense Labs
S
Schneier on Security
Security Archives - TechRepublic
Security Archives - TechRepublic
L
Lohrmann on Cybersecurity
V
Visual Studio Blog
云风的 BLOG
云风的 BLOG
WordPress大学
WordPress大学
The Register - Security
The Register - Security
N
Netflix TechBlog - Medium
Hugging Face - Blog
Hugging Face - Blog
Project Zero
Project Zero
博客园 - 叶小钗
F
Full Disclosure
大猫的无限游戏
大猫的无限游戏
Latest news
Latest news
S
SegmentFault 最新的问题
C
Cyber Attacks, Cyber Crime and Cyber Security
Google Online Security Blog
Google Online Security Blog
Recorded Future
Recorded Future
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Hacker News - Newest:
Hacker News - Newest: "LLM"
腾讯CDC
L
LINUX DO - 最新话题
Google DeepMind News
Google DeepMind News
P
Privacy International News Feed
I
InfoQ
F
Fortinet All Blogs
Vercel News
Vercel News
H
Hackread – Cybersecurity News, Data Breaches, AI and More
T
Threatpost
T
Tenable Blog
B
Blog RSS Feed

Dmitri Pavlutin Blog

Pure Functions in JavaScript: A Beginner's Guide Record Type in TypeScript: A Quick Intro How to Write Comments in React: The Good, the Bad and the Ugly 4 Ways to Create an Enum in JavaScript React forwardRef(): How to Pass Refs to Child Components TypeScript Function Types: A Beginner's Guide How to Use v-model to Access Input Values in Vue Mastering Vue refs: From Zero to Hero Environment Variables in JavaScript: process.env 5 Must-Know Differences Between ref() and reactive() in Vue How to Destructure Props in Vue (Composition API) Triangulation in Test-Driven Development How to Use nextTick() in Vue Programming to Interface Vs to Implementation A Smarter JavaScript Mapper: array.flatMap() Array Grouping in JavaScript: Object.groupBy() How to Access ES Module Metadata using import.meta JSON Modules in JavaScript How to Trim Strings in JavaScript TypeScript Function Overloading How to Debounce and Throttle Callbacks in Vue How to Show/Hide Elements in Vue Sparse vs Dense Arrays in JavaScript How to Fill an Array with Initial Values in JavaScript Covariance and Contravariance in TypeScript What are Higher-Order Functions in JavaScript? How to Use TypeScript with React Components Index Signatures in TypeScript unknown vs any in TypeScript A Guide to React Context and useContext() Hook How to Use Promise.any() 2 Ways to Remove a Property from an Object in JavaScript 'return await promise' vs 'return promise' in JavaScript How to Use Promise.allSettled() How to Use fetch() with JSON JavaScript Promises: then(f,f) vs then(f).catch(f) What is a Promise in JavaScript? How to Use Promise.all() A Simple Guide to Component Props in React Don't Stop Me Now: How to Use React useTransition() hook A Simple Explanation of JavaScript Variables: const, let, var ES Modules Dynamic Import How to Memoize with React.useMemo() How to Cleanup Async Effects in React Why Math.max() Without Arguments Returns -Infinity How to Debounce and Throttle Callbacks in React Don't Confuse Function Expressions and Function Declarations in JavaScript How to Use ES Modules in Node.js Solving a Mystery Behavior of parseInt() in JavaScript How to Use Array Reduce Method in JavaScript 3 Ways to Merge Arrays in JavaScript A Guide to Jotai: the Minimalist React State Management Library The Difference Between Values and References in JavaScript How to Implement a Queue in JavaScript A Helpful Algorithm to Determine "this" value in JavaScript React useRef() Hook Explained in 3 Steps 7 Interview Questions on "this" keyword in JavaScript. Can You Answer Them? How to Greatly Enhance fetch() with the Decorator Pattern 7 Interview Questions on JavaScript Closures. Can You Answer Them? What's a Method in JavaScript? array.sort() Does Not Simply Sort Numbers in JavaScript How to Solve the Infinite Loop of React.useEffect() The New Array Method You'll Enjoy: array.at(index) What's the Difference between DOM Node and Element? Why Promises Are Faster Than setTimeout()? Everything About Callback Functions in JavaScript How React Updates State 5 Mistakes to Avoid When Using React Hooks 5 Best Practices to Write Quality JavaScript Variables Type checking in JavaScript: typeof and instanceof operators 3 Ways to Check if a Variable is Defined in JavaScript React Forms Tutorial: Access Input Values, Validate, Submit Forms Prototypal Inheritance in JavaScript How to Timeout a fetch() Request How to Learn JavaScript If You're a Beginner A Simple Explanation of React.useEffect() A Simple Explanation of JavaScript Iterators How to Use React Controlled Inputs Everything about null in JavaScript How to Use Fetch with async/await Getting Started with Arrow Functions in JavaScript An Interesting Explanation of async/await in JavaScript Front-end Architecture: Stable and Volatile Dependencies Is it Safe to Compare JavaScript Strings? How to Access Object's Keys, Values, and Entries in JavaScript What Actually is a String in JavaScript? 3 Ways to Shallow Clone Objects in JavaScript (w/ bonuses) Checking if an Array Contains a Value in JavaScript JavaScript Event Delegation: A Beginner's Guide How to Parse URL in JavaScript: hostname, pathname, query, hash 3 Ways to Detect an Array in JavaScript How to Get the Screen, Window, and Web Page Sizes in JavaScript 3 Ways to Check If an Object Has a Property/Key in JavaScript How to Compare Objects in JavaScript Object.is() vs Strict Equality Operator in JavaScript Own and Inherited Properties in JavaScript 5 Differences Between Arrow and Regular Functions How to Use Object Destructuring in JavaScript Your Guide to React.useCallback() 5 JavaScript Scope Gotchas
How to Use React useReducer() Hook
Dmitri Pavlutin · 2021-09-15 · via Dmitri Pavlutin Blog

If you've used useState() hook to manage a non-trivial state like a list of items, where you need to add, update and remove items in the state, you can notice that the state management logic takes a good part of the component's body.

A React component should usually contain the logic that calculates the output. But the state management logic is a different concern that should be managed in a separate place. Otherwise, you get a mix of state management and rendering logic in one place, and that's difficult to read, maintain, and test!

To help you separate the concerns (rendering and state management) React provides the hook useReducer(). The hook does so by extracting the state management out of the component.

Let's see how the useReducer() hook works with accessible real-world examples.

Table of Contents

  • 1. useReducer()
  • 2. A stopwatch
  • 3. A mental model
  • 4. Conclusion

1. useReducer()

The useReducer(reducer, initialState) hook accepts 2 arguments: the reducer function and the initial state. The hook then returns an array of 2 items: the current state and the dispatch function.


import { useReducer } from 'react';

function MyComponent() {

const [state, dispatch] = useReducer(reducer, initialState);

const action = {

type: 'ActionType'

};

return (

<button onClick={() => dispatch(action)}>

Click me

</button>

);

}


Now, let's decipher what the terms of initial state, action object, dispatch, and reducer mean.

A. Initial state

The initial state is the value the state is initialized with.

For example, in the case of a counter state, the initial value is:


// initial state

const initialState = {

counter: 0

};


B. Action object

An action object is an object that describes how to update the state.

Typically, the action object has a property type — a string describing what kind of state update the reducer must do.

For example, an action object to increase the counter can look as follows:


const action = {

type: 'increase'

};


If the action object must carry some useful information (aka payload) to be used by the reducer, then you can add additional properties to the action object.

For example, here's an action object meant to add a new user to the users state array:


const action = {

type: 'add',

user: {

name: 'John Smith',

email: 'jsmith@mail.com'

}

};


user is a property that holds the information about the user to add.

The action object is interpreted by the reducer function (described below).

C. Dispatch function

The dispatch is a special function that dispatches an action object.

The dispatch function is created for you by the useReducer() hook:


const [state, dispatch] = useReducer(reducer, initialState);


Whenever you want to update the state (usually from an event handler or after completing a fetch request), you simply call the dispatch function with the appropriate action object: dispatch(actionObject).

In simpler terms, dispatching means a request to update the state.

D. Reducer function

The reducer is a pure function that accepts 2 parameters: the current state and an action object. Depending on the action object, the reducer function must update the state in an immutable manner, and return the new state.

The following reducer function supports the increase and decrease of a counter state:


function reducer(state, action) {

let newState;

switch (action.type) {

case 'increase':

newState = { counter: state.counter + 1 };

break;

case 'decrease':

newState = { counter: state.counter - 1 };

break;

default:

throw new Error();

}

return newState;

}


The reducer above doesn't modify directly the current state in the state variable, but rather creates a new state object stored in newState, then returns it.

React checks the difference between the new and the current state to determine whether the state has been updated. So do not mutate the current state directly.

E. Wiring everything

Wiring all these terms together, here's how the state update using a reducer works.

React useReducer()

The dispatch function with the action object is called as a result of an event handler or completing a fetch request.

Then React redirects the action object and the current state value to the reducer function.

The reducer function uses the action object and performs a state update, returning the new state.

React then checks whether the new state differs from the previous one. If the state has been updated, React re-renders the component, and useReducer() returns the new state value: [newState, ...] = useReducer(...).

Note that useReducer() design is based on the Flux architecture.

If all these terms sound too abstract, no worries! Let's see how useReducer() works in an interesting example.

2. A stopwatch

Let's implement a stopwatch having 3 buttons: Start, Stop, Reset, and a number displaying the passed seconds.

Now let's think about structuring the state of the stopwatch.

There are 2 important state properties: a boolean indicating whether the stopwatch runs — isRunning, and a number indicating the number of passed seconds — time. Here's how the initial state can look like:


const initialState = {

isRunning: false,

time: 0

};


The initial state indicates that the stopwatch is initialized as inactive and at 0 seconds.

Then let's consider what action objects the stopwatch should have. You need 4 kinds of actions: to start, stop and reset the stopwatch running process, as well as tick the time each second.


// The start action object

{ type: 'start' }

// The stop action object

{ type: 'stop' }

// The reset action object

{ type: 'reset' }

// The tick action object

{ type: 'tick' }


Having the state structure, as well as the possible actions, let's use the reducer function to define how the action objects update the state:


function reducer(state, action) {

switch (action.type) {

case 'start':

return { ...state, isRunning: true };

case 'stop':

return { ...state, isRunning: false };

case 'reset':

return { isRunning: false, time: 0 };

case 'tick':

return { ...state, time: state.time + 1 };

default:

throw new Error();

}

}


Finally, here's the component Stopwatch that wires everything together by invoking the useReducer() hook:


import { useReducer, useEffect, useRef } from 'react';

function Stopwatch() {

const [state, dispatch] = useReducer(reducer, initialState);

const idRef = useRef(0);

useEffect(() => {

if (!state.isRunning) {

return;

}

idRef.current = setInterval(() => dispatch({ type: 'tick' }), 1000);

return () => {

clearInterval(idRef.current);

idRef.current = 0;

};

}, [state.isRunning]);

return (

<div>

{state.time}s

<button onClick={() => dispatch({ type: 'start' })}>

Start

</button>

<button onClick={() => dispatch({ type: 'stop' })}>

Stop

</button>

<button onClick={() => dispatch({ type: 'reset' })}>

Reset

</button>

</div>

);

}


Open the demo.

The click event handlers of the Start, Stop, and Reset buttons correspondingly use the dispatch() function to dispatch the corresponding action object.

Inside the useEffect() callback, if state.isRunning is true, the setInterval() timer function dispatches the tick action object each second dispatch({type: 'tick'}).

Each time the reducer() function updates the state, the component receives the new state and re-renders.

3. A mental model

To solidify your knowledge even more, let's see a real-world example that works similarly to a reducer.

Imagine you're the captain of a ship in the first half of the 20th century.

Engine Order Telegraph

The captain's bridge has a special communication device called engine order telegraph (see the picture above). This communication tool is used to transmit commands from the bridge to the engine room. Typical commands would be to move back slowly, move ahead half power, stop, etc.

You're on the bridge and the ship is at full stop. You (the captain) want the ship to move forward at full speed. You'd approach the engine order telegraph and set the handle to ahead full. The engineers in the engine room, having the same device, see the ahead full command, and set the engine to the corresponding regime.

The engine order telegraph is the dispatch function, the commands are the action objects, the engineers in the engine room are the reducer function, and the engine regime is the state.

The engine order telegraph helps separate the bridge from the engine room. In the same way the useReducer() hook helps separate the rendering from the state management logic.

4. Conclusion

The useReducer() hook lets you separate the state management from the rendering logic of the component.

const [state, dispatch] = useReducer(reducer, initialState) accepts 2 arguments: the reducer function and the initial state. Also, the reducer returns an array of 2 items: the current state and the dispatch function.

To update the state call dispatch(action) with the appropriate action object. The action object is then forwarded to the reducer() function that updates the state. If the state has been updated by the reducer, then the component re-renders, and [state, ...] = useReducer(...) hook returns the new state value.

useReducer() fits great to deal with complex state management (requiring at least 2-3 update actions). For simple state management, I recommend useState().

How often do you use useReducer() compared to useState()?