let formValue = '';
const log = [];
// Simulates useCallback(() => validate(formValue), [])
// Bug: empty deps — formValue never updates in handler
function createSubmitHandler() {
const capturedValue = formValue; // '' at creation
return () => {
if (!capturedValue) {
log.push('invalid: empty');
} else {
log.push('valid: ' + capturedValue);
}
};
}
const handleSubmit = createSubmitHandler(); // created once, [] deps
formValue = 'Alice'; // user types
handleSubmit(); // still reads ''
formValue = 'Bob';
handleSubmit();let formValue = '';
const log = [];
// Fix: include formValue in deps by recreating handler when it changes
function createSubmitHandler(currentValue) {
return () => {
if (!currentValue) {
log.push('invalid: empty');
} else {
log.push('valid: ' + currentValue);
}
};
}
formValue = 'Alice';
let handleSubmit = createSubmitHandler(formValue); // recreated with new value
handleSubmit();
formValue = 'Bob';
handleSubmit = createSubmitHandler(formValue);
handleSubmit();Bug: capturedValue is '' because createSubmitHandler was called before formValue was updated. Empty deps ([]) means the handler is never recreated.
Explanation: Recreating the handler with the current value as a parameter breaks the stale closure. In React, this means adding formValue to useCallback's dep array.
Key Insight: useCallback with empty deps creates a permanent stale closure. Add all referenced state/props to deps. The linter rule exhaustive-deps catches this automatically.