const log = [];
// Wrong: spreading creates new object — closure reference is lost
function setup() {
let user = { name: '' };
// Handler closes over the SPECIFIC user object at creation
const handler = () => log.push('user: ' + user.name);
// Simulate setState — React replaces the object
user = { ...user, name: 'Alice' }; // NEW object — handler still points to old!
user = { ...user, name: 'Bob' }; // another new object
handler(); // reads old user (original empty object)
}
setup();
console.log(log[0]);const log = [];
// Correct: use ref pattern — mutation keeps the same reference
function setup() {
const userRef = { current: { name: '' } };
// Handler reads from ref — always gets latest
const handler = () => log.push('user: ' + userRef.current.name);
// Update via ref — same object, updated property
userRef.current = { ...userRef.current, name: 'Alice' };
userRef.current = { ...userRef.current, name: 'Bob' };
handler(); // reads latest via ref
}
setup();
console.log(log[0]);Bug: handler closes over the initial user object reference. React state updates (spread) create new objects — the old reference is stale and still has name: ''.
Explanation: handler reads userRef.current at call time, not at creation time. The ref is the stable container — its contents can change while the reference remains valid.
Key Insight: When you need a callback to always see the latest state without recreating it (e.g., in a long-lived subscription), store state in a ref alongside the actual state: useEffect(() => { latestRef.current = state; }).