import { useState } from "react";
type Rule = {
value: string;
valid: boolean;
};
type RuleComponentProps = {
rule: Rule;
setValue: (value: string) => void;
setValid: (valid: boolean) => void;
};
function RuleComponent({ rule, setValue, setValid }: RuleComponentProps) {
function validator(value: string) {
rule.valid = !!value;
// Why below line doesn't work?
// setValid(!!value);
}
function handleChange(e: any) {
const value = e.target.value;
validator(value);
setValue(value);
// rule.value = value;
}
return <input onChange={handleChange} value={rule.value} />;
}
export default function App() {
const [ruleList, setRuleList] = useState<Rule[]>([]);
function addRule() {
setRuleList((prev) => [...prev, { value: "", valid: false }]);
}
function getSetValueMeth(index: number) {
return (value: string) => {
const newRuleList = ruleList.map((rule, i) => {
if (index === i) {
return { ...rule, value };
}
return rule;
});
setRuleList(newRuleList);
};
}
function getSetValidMeth(index: number) {
return (valid: boolean) => {
const newRuleList = ruleList.map((rule, i) => {
if (index === i) {
return { ...rule, valid };
}
return rule;
});
setRuleList(newRuleList);
};
}
return (
<div className="App">
<div>
<button onClick={() => addRule()}>Add rule</button>
</div>
{ruleList.map((rule, index) => (
<p key={index}>
Rule{index}:{" "}
<RuleComponent
rule={rule}
setValue={getSetValueMeth(index)}
setValid={getSetValidMeth(index)}
/>
</p>
))}
<p>{JSON.stringify(ruleList)}</p>
<button onClick={() => console.log(ruleList)}>Show Rule List</button>
</div>
);
}
I'm trying to edit the valid value in rulelist in above code. It's working when using rule.valid = !!value;, but when I change to setValid(!!value); method, the valid value didn't change even the setRuleList hooks called and with right updated newRuleList.
I created a CodeSandbox for this: https://codesandbox.io/p/sandbox/react-demo-dfs236?file=%2Fsrc%2FApp.tsx%3A17%2C29, you can debug it directly.
I know it's not proper to edit value directly in React, but why the hook not work? What's the proper way to accomplish my feature?
Issue
The enqueued state update by the
setValidcall invalidator(getSetValidMeth) is wiped out by the enqueued state update by thesetValuecall inhandleChangesince each has a closure over the un-updatedruleListstate value at the timehandleChangeis called.Using
rule.valid = !!value;was mutating the current state reference so it persisted through to the next render cycle.Solution
Use a functional state update so each enqueued update correctly updates from any previous state value instead of whatever is closed over in callback scope.
Full code: