Essentially I am wanting to have the Parent element control the value of its children. Through using React inputs previously, I know that you need to pass an onChange prop so that the Parent can update the value it passes down to its children.
I've made the value and onChange props optional, as I also want to support the case that you use the Child as an uncontrolled input.
Both the uncontrolled, and the unhandled controlled variants of the Child work. However the handled controlled variant does not.
Any ideas how to get this to work? I've linked my JSFiddle below with my minimal example showing my problem:
https://jsfiddle.net/numberjak/wufdh0ys/3/
Code:
const Parent = () => {
const [parentValue, setParentValue] = React.useState(0);
return (
<div>
Should act like uncontrolled input (i.e. value should change) [WORKS]
<Child />
Should act like controlled input (i.e. value should change because onChange handler passed in) [DOESN'T WORK]
<Child value={parentValue} onChange={setParentValue} />
Should act like controlled input, except no onChange handler means value shouldn't change [WORKS]
<Child value={5} />
</div>
);
};
const Child = ({value, onChange}) => {
const [childValue, setChildValue] = React.useState(value !== undefined ? value : 0);
const handleOnChange = (newValue) => {
if (value === undefined) {
setChildValue(newValue);
}
if (onChange !== undefined) {
onChange(newValue);
}
};
return (
<GrandChild value={childValue} onChange={handleOnChange} />
);
};
const GrandChild = ({value, onChange}) => {
const handleOnClick = () => {
onChange(value + 1);
};
return (
<div className="grand-child" onClick={handleOnClick}>{value}</div>
);
};
ReactDOM.render(<Parent />, document.querySelector("#app"))
This happens because the value
prop that you pass to Child
component is used only as an initial value for useState
hook. On the next updates useState
will ignore this value because childValue
has been already initialized. To update your state variable childValue
you need to use useEffect
in which you have to call setChildValue
:
const Child = ({value, onChange}) => {
const [childValue, setChildValue] = React.useState(value !== undefined ? value : 0);
React.useEffect(() => {
value !== undefined && setChildValue(value);
}, [value]);
...
}