-
Notifications
You must be signed in to change notification settings - Fork 46.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[React 19]useEffect cleaned the wrong state in StrictMode #31098
Comments
This issue seems interesting! I would love to contribute or discuss further. 👍 |
This comment was marked as spam.
This comment was marked as spam.
Is this issue still opened ? |
Yes, neither React 19.x nor 18.x has solved this issue |
A reduced repro of this change is import { useState } from "react";
let stateId = 0;
export default function App() {
stateId++;
const [state] = useState(stateId);
return state;
} What is happening here is that in React the App component is rendered twice. In 19 React preserves the state from the first render. I think this is intentional by React preserving and re-using more state, but someone else might correct me. |
It seems there's an issue with how the useEffect cleanup function references the state in StrictMode. The current implementation logs the initial stateId during cleanup, which can lead to confusion since stateId increments with each render. To fix this, consider using an updateState function that retains the destroy method while creating a new state with an incremented stateId. This ensures the cleanup function references the correct state:` import { useEffect, useState } from "react"; let stateId = 0; export default function App() { useEffect(() => { const updateState = () => { console.log("render state", state); return ( state: {state.stateId} Update State ); } ` |
A more practical example //In this example, it is a websocket connection, which can also be any object that is not suitable for creating in each rendering and should be destroyed when not use
class Connection {
constructor() {
// create ws connection
}
destroy() {
// close ws connection
}
}
function initState() {
return new Connection();
}
export default function App() {
const [connection, setConnection] = useState(initState);
useEffect(() => {
return () => {
connection.destroy();
};
}, [state]);
const reconnect = () => setConnection(initState())
return null;
} Of course, we can easily solve this problem through the following way: // init connection in useEffect
export default function App() {
const [connection, setConnection] = useState();
useEffect(() => {
const newConnection = initState();
setConnection(newConnection)
return () => {
newConnection.destroy();
};
}, [state]);
// But the type of connection become `Connection | undefined`,
// This will add some additional check logic
useMemo(() => {
if (!connection) return null; // <-
return connection;
}, []);
return null;
} |
Summary
https://codesandbox.io/p/sandbox/youthful-orla-lk7g89
console output:
Final render:
state: 1
, the destroyed state[email protected] behavior
related issue: #29634
Final render:
state: 2
, the destroyed stateThe text was updated successfully, but these errors were encountered: