On every click of increment
button:
Expectation: current count
is logged
Reality: initial value of count
, ie 3 is logged
import React, { useState, useEffect } from "react";
function SomeLibrary(props) {
const [mapState, setMapState] = useState(undefined);
useEffect(() => {
console.log("setting map");
// Run exactly once at mount of component
setMapState(props.map);
}, []);
useEffect(() => {
if (mapState) {
mapState.key();
}
}, [props]);
return <div> ... </div>;
}
export default function App() {
const [count, setCount] = React.useState(3);
const map = { key: () => {
console.log("fn", count);
}};
return (
<div>
Active count: {count} <br />
<button onClick={() => {
setCount(count + 1);
}}
>
Increment
</button>
<SomeLibrary map={map} />
</div>
);
}
Does the object in JS locks the values of variables inside it after initializing?
I want to know the reason why function in object doesn't use the current value of count whenever invoked but React ref gets the current value in that same scenario
I don't understand why this works:
Replace the map variable with this:
const [count, setCount] = React.useState(3);
const stateRef = useRef();
stateRef.current = count;
const map = { key: () => {
console.log("fn", stateRef.current);
}};
No.
You're effectively setting state of SomeLibrary
with an initial value when it mounts, and never again updating that state, so it continually logs its initial value.
const [mapState, setMapState] = useState(undefined);
useEffect(() => {
console.log("setting map");
// Run only once at mount of component
setMapState(props.map); // <-- no other `setMapState` exists
}, []); // <-- runs once when mounting
By simply adding props.map
to the dependency array this effect runs only when map
updates, and correctly updates state.
useEffect(() => {
console.log("setting map");
// Run only once at mount of component
setMapState(props.map);
}, [props.map]);
Notice, however, the state of SomeLibrary
is a render cycle behind that of App
. This is because the value of the queued state update in SomeLibrary
isn't available until the next render cycle. It is also an anti-pattern to store passed props in local component state ( with few exceptions ).
const [count, setCount] = React.useState(3);
const stateRef = useRef();
stateRef.current = count; // <-- stateRef is a stable reference
const map = { key: () => {
console.log("fn", stateRef.current); // <-- ref enclosed in callback
}};
When react component props or state update, a re-render is triggered. The useRef
does not, it's used to hold values between or through render cycles, ie it is a stable object reference. This reference is enclosed in the callback function in the map object passed as a prop. When the count
state updates in App
a rerender is triggered and stateRef.current = count;
updates the value stored in the ref, ie this is akin to an object mutation.
Another piece to the puzzle is functional components are always rerendered when their parent rerenders. The passed map
object is a new object when passed in props.
It's this rerendering that allows SomeLibrary
to run the second effect to invoke the non-updated-in-state callback mapState.key
, but this time the object reference being console logged has been mutated.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.