Description
BUG
What is the current behavior?
Example: https://codesandbox.io/s/6y1x2zr21n clicking on OK button cause Warning: Can't perform a React state update on an unmounted component.
The problem that unsubscribe is called during B event setVisible(v => false);
call, see logs:
SET VISIBLE BEFORE
UNSUBSCRIBE
Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
in Child (created by Holder)
SET VISIBLE AFTER
In our case we have this even without RAF call, but on transitionend
DOM event.
(It's occurred randomly and rare in our codebase as transitionend event should be called exactly at needed time, but example showed what happens)
Seems like it occurred only if you have a setState
call during useEffect callback like setRefresh(v => v + 1);
(inside provided code) (after rewriting our codebase to avoid setState calls in useEffect the error has gone)
Code
import React from "react";
import ReactDOM from "react-dom";
import mitt from "mitt";
const emitter = mitt();
const Child = () => {
const [visible, setVisible] = React.useReducer((s, a) => a, true);
React.useEffect(() => {
const handle = () => {
console.log("SET VISIBLE BEFORE");
setVisible(v => false); // <--- THIS CALL CAUSES UNSUBSCRIBE AND WARNING ABOUT STATE
console.log("SET VISIBLE AFTER");
};
emitter.on("B", handle);
return () => {
console.log("UNSUBSCRIBE");
emitter.off("B", handle);
};
}, []);
return <div>{visible && <h1>CHILD TEXT</h1>}</div>;
};
const Holder = () => {
const [refresh, setRefresh] = React.useState(0);
const visible = React.useRef(true);
React.useEffect(() => {
if (refresh === 1) {
visible.current = false;
setRefresh(v => v + 1); // <--- This state change from effect caused problems
}
const handle = () => {
setRefresh(v => v + 1);
};
emitter.on("A", handle);
return () => {
emitter.off("A", handle);
};
});
return <div>{visible.current && <Child />}</div>;
};
function App() {
return (
<div>
<Holder />
<button
onClick={() => {
emitter.emit("A", {});
requestAnimationFrame(() => {
emitter.emit("B", {});
});
}}
>
OK
</button>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
What is the expected behavior?
Do not provide warning if unsubscription is called during "setState" call.
Which versions of React, and which browser / OS are affected by this issue? Did this work in previous versions of React?
React 16.7.0-alpha.2