2

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"))

2 Answers 2

3

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]);

...
}
Sign up to request clarification or add additional context in comments.

1 Comment

Ace - thank you! Didn't realise useState ignores the initialising value once it's already been initialised once
0

If <Child /> is not a complex, expensive to render element (like in this case), you could add a key to it pointing at the value.

const Parent = () => {
...
    Should act like controlled input (i.e. value should change because onChange handler passed in) [DOESN'T WORK]
    <Child key={parentValue} value={parentValue} onChange={setParentValue} />
...
}

What React does here is now any time parentValue changes, React deletes the <Child /> with that key and adds an identical one, but now with that new value (that is, you "reset" the state to <Child />, since in the end this is actually a new child, and it's cheap to add to DOM since it's almost the same as before, just the value changed).

See more here.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.