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

推荐订阅源

U
Unit 42
P
Proofpoint News Feed
The Last Watchdog
The Last Watchdog
S
Secure Thoughts
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
N
News | PayPal Newsroom
Application and Cybersecurity Blog
Application and Cybersecurity Blog
O
OpenAI News
S
Security @ Cisco Blogs
宝玉的分享
宝玉的分享
Hacker News: Ask HN
Hacker News: Ask HN
T
Troy Hunt's Blog
Google Online Security Blog
Google Online Security Blog
WordPress大学
WordPress大学
有赞技术团队
有赞技术团队
TaoSecurity Blog
TaoSecurity Blog
Help Net Security
Help Net Security
Latest news
Latest news
NISL@THU
NISL@THU
S
Security Affairs
博客园_首页
C
CXSECURITY Database RSS Feed - CXSecurity.com
博客园 - 聂微东
AI
AI
www.infosecurity-magazine.com
www.infosecurity-magazine.com
Recent Announcements
Recent Announcements
P
Privacy & Cybersecurity Law Blog
小众软件
小众软件
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
Hugging Face - Blog
Hugging Face - Blog
博客园 - 司徒正美
AWS News Blog
AWS News Blog
W
WeLiveSecurity
Google DeepMind News
Google DeepMind News
I
InfoQ
Schneier on Security
Schneier on Security
Recent Commits to openclaw:main
Recent Commits to openclaw:main
T
The Exploit Database - CXSecurity.com
IT之家
IT之家
T
Threatpost
Scott Helme
Scott Helme
L
LINUX DO - 热门话题
腾讯CDC
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
N
News and Events Feed by Topic
L
LINUX DO - 最新话题
F
Full Disclosure
大猫的无限游戏
大猫的无限游戏
H
Heimdal Security Blog
S
SegmentFault 最新的问题

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 How to Use React useReducer() Hook 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() 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 Cleanup Async Effects in React
Dmitri Pavlutin · 2021-05-25 · via Dmitri Pavlutin Blog

The common asynchronous side-effects are: performing fetch requests to load data from a remote server, handle timers like setTimeout(), debounce or throttle functions, etc.

Handling the side-effects in React is a medium-complexity task. However, from time to time you might have difficulties at the intersection of component lifecycle (initial render, mount, update, unmount) and the side-effect lifecycle (start, in progress, complete).

One such difficulty is when a side-effect completes and tries to update the state of an already unmounted component. This leads to a React warning:


Warning: Can't perform a React state update on an unmounted component.


In this post, I'll show you when the above warning appears and how to correctly clean side-effects in React.

1. State update after unmounting

Let's reproduce the state update after unmounting problem in an example.

An application shows information about a local restaurant. The first page displays a list of employees (waiters, kitchen staff), and the second page shows textual information.

The employees list is loaded using a fetch request.

Here's the initial implementation of <Employees> and <About> components:


import { useState, useEffect } from 'react';

function Employees() {

const [list, setList] = useState(null);

useEffect(() => {

(async () => {

try {

const response = await fetch('/employees/list');

setList(await response.json());

} catch (e) {

// Some fetch error

}

})();

}, []);

return (

<div>

{list === null ? 'Fetching employees...' : ''}

{list?.map(name => <div>{name}</div>)}

</div>

);

}

function About() {

return (

<div>

<p>Our restaurant is located ....</p>

</div>

);

}


The <App> component wires together <Employees> and <About>:


import { useState } from 'react';

function App() {

const [page, setPage] = useState('employees');

const showEmployeesPage = () => setPage('employees');

const showAboutPage = () => setPage('about');

return (

<div className="App">

<h2>My restaurant</h2>

<a href="#" onClick={showEmployeesPage}>Employees Page</a>

<a href="#" onClick={showAboutPage}>About Page</a>

{page === 'employees' ? <Employees /> : <About />}

</div>

);

}


Open the demo.

Open the demo of the application, and before the employees' fetching completes, click the About Page link. Then open the console, and notice that React has thrown a warning:

React warning about updating the state of unmounted component

The reason for this warning is that <Employees> component has already been unmounted, but still, the side-effect that fetches employees completes and updates the state of an unmounted component.


function Employees() {

const [list, setList] = useState(null);

useEffect(() => {

(async () => {

try {

const response = await fetch('/employees/list');

// Updating the state of an unmounted component

setList(await response.json());

} catch (e) {

// Some fetch error

}

})();

}, []);

// ...

}


What would be the solution to the issue? As the warning suggests, you need to cancel any active asynchronous tasks if the component unmounts. Let's see how to do that in the next section.

2. Cleanup the fetch request

Fortunately, useEffect(callback, deps) allows you to easily cleanup side-effects. When the callback function returns a function, React will use that as a cleanup function:


function MyComponent() {

useEffect(() => {

// Side-effect logic...

return () => {

// Side-effect cleanup

};

}, []);

// ...

}


Also, in order to cancel an active fetch request, you need to use an AbortController instance.

Let's wire the above ideas and fix the <Employees> component to correctly handle the cleanup of the fetch async effect:


import { useState, useEffect } from 'react';

function Employees() {

const [list, setList] = useState(null);

useEffect(() => {

let controller = new AbortController();

(async () => {

try {

const response = await fetch('/employees/list', {

signal: controller.signal

});

setList(await response.json());

controller = null;

} catch (e) {

// Handle fetch error

}

})();

return () => controller?.abort();

}, []);

return (

<div>

{list === null ? 'Fetching employees...' : ''}

{list?.map(name => <div>{name}</div>)}

</div>

);

}


Open the demo.

let controller = new AbortController() creates an instance of the abort controller. Then await fetch(..., { signal: controller.signal }) connects the controller with the fetch request.

Finally, the useEffect() callback returns a cleanup function () => controller?.abort() that aborts the request in case if the component umounts.

Open the fixed demo, and, before the employees fetch request completes, click the About Page link. Now if you check the console, there aren't going to be any warnings: because the fetch request is aborted when <Employess> component unmounts.

3. Cleanup on prop or state change

While in the restaurant application the side-effect cleanup happens when the component unmounts, there might be cases when you want to abort a fetch request on component update. That might happen, for example, when the side-effect depends on a prop.

For example, consider the following component <EmployeeDetails> that accepts a prop id. The component makes a fetch request to load the details of an employee by id:


import { useState, useEffect } from 'react';

function EmployeeDetails({ id }) {

const [employee, setEmployee] = useState(null);

useEffect(() => {

let controller = new AbortController();

(async () => {

try {

const response = await fetch(`/employees/${id}`, {

signal: controller.signal

});

setEmployee(await response.json());

controller = null;

} catch (e) {

// Handle fetch error

}

})();

return () => controller?.abort();

}, [id]);

if (employee === null) {

return <div>Fetching employee...</div>;

}

return (

<div>

Employee name: {employee.name}

</div>

);

}


The fetch request uses id prop await fetch(`/employees/${id}`, ...). If the id prop changes while there's already a request in progress, you might want to abort the outdated already request.

It's up to you to decide whether or not it worth aborting the requests that generated by prop or state changes. The rule of thumb is that the heaver and the longer it takes the request to complete, the better chances are that it needs cancelling.

4. Common side-effects that need cleanup

There are common asynchronous side-effects that are recommended to cleanup.

4.1 Fetch requests

As already mentioned, it is recommended to abort the fetch request when the component unmounts or updates.


import { useState, useEffect } from 'react';

function MyComponent() {

const [value, setValue] = useState();

useEffect(() => {

let controller = new AbortController();

(async () => {

try {

const response = await fetch('/api', {

signal: controller.signal

});

setValue(await response.json());

controller = null;

} catch (e) {

// Handle fetch error

}

})();

return () => controller?.abort();

}, []);

// ...

}


Check the section Canceling a fetch request to find more information on how to properly cancel fetch requests.

4.2 Timer functions

When using setTimeout(callback, time) or setInterval(callback, time) timer functions, it's usually a good idea to clear them on unmount using the special clearTimeout(timerId) function.


import { useState, useEffect } from 'react';

function MyComponent() {

const [value, setValue] = useState('');

useEffect(() => {

let timerId = setTimeout(() => {

setValue('New value');

timerId = null;

}, 3000);

return () => clearTimeout(timerId);

}, []);

// ...

}


4.3 Debounce and throttle

When debouncing or throttling event handlers in React, you may also want to make sure to clear any scheduled call of the debounced or throttled functions.

Usually the debounce and throttling implementions (e.g. lodash.debounce, lodash.throttle) provide a special method cancel() that you can call to stop the scheduled execution:


import { useState, useEffect } from 'react';

import throttle from 'lodash.throttle';

function MyComponent () {

useEffect(() => {

const handleResize = throttle(() => {

// Handle window resize...

}, 300);

window.addEventListener('resize', handleResize);

return () => {

window.removeEventListener('resize', handleResize);

handleResize.cancel();

};

}, []);

// ...

}


4.4 Web sockets

Another good candidate requiring cleanup are the web sockets:


import { useState } from 'react';

function MyComponent() {

const [value, setValue] = useState();

useEffect(() => {

const socket = new WebSocket("wss://www.example.com/ws");

socket.onmessage = (event) => {

setValue(JSON.parse(event.data));

};

return () => socket.close();

}, []);

// ...

}


5. Conclusion

I recommend cleaning async effects when the component unmounts. Also, if the async side-effect depends on prop or state values, then consider cleaning them when the component updates too.

Depending on the type of the side-effect (fetch request, timeout, etc) return a cleanup function from the useEffect() callback that is going to clean the side-effect.


function MyComponent() {

useEffect(() => {

// Side-effect logic...

return () => {

// Side-effect cleanup...

};

}, []);

// ...

}


What other async effects that need cleanup do you know? Write a comment below!