Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Nested Components not updating their state

I'm having some trouble updating nested components in my tree structure. I have created the following minimal example to illustrate the problem: Codesandbox.io

For completeness sake, this is the component that's being nested:

class Node extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      selected: props.selected
    };
    this.toggleSelected = this.toggleSelected.bind(this);
  }

  toggleSelected() {
    this.setState({
      selected: !this.state.selected
    });
  }

  render() {
    return (
      <>
        <div onClick={this.toggleSelected}>
          Selected: {this.state.selected ? "T" : "F"}, Depth: {this.props.depth}
        </div>

        {this.props.depth < 5 && (
          <Node selected={this.state.selected} depth={this.props.depth + 1} />
        )}
      </>
    );
  }
}

Nodes in the tree should be selectable on click and I'd like to toggle the selected state in all children (recursively) aswell. I thought I could do this by passing this.state.selected via props to the child/children, unfortunately this doesn't seem to work.

The children get re-rendered, however using their old state (understandibly, as they're not being re-initialized via the constructor). What would be the correct way to handle this?

I've tried passing the key prop to the nodes aswell to help react distinguish the elements, to no avail.

Edit: Here are a few examples of desired behaviour:

Consider this tree:

[ ] Foo
    [ ] Foo A
        [ ] Foo A1
        [ ] Foo A2
    [ ] Foo B
        [ ] Foo B1
        [ ] Foo B2

Expected result when checking "Foo"-Node:

[x] Foo
    [x] Foo A
        [x] Foo A1
        [x] Foo A2
    [x] Foo B
        [x] Foo B1
        [x] Foo B2

Expected result when checking "Foo A"-Node:

[ ] Foo
    [x] Foo A
        [x] Foo A1
        [x] Foo A2
    [ ] Foo B
        [ ] Foo B1
        [ ] Foo B2

Any tips / hints in the right direction are appreciated.

like image 231
ccKep Avatar asked Oct 16 '22 09:10

ccKep


1 Answers

You should use getDerivedStateFromProps like this:

    constructor(props) {
       super(props);
       this.state = {
         selected: props.selected,
         propsSelected: props.selected
       };
       this.toggleSelected = this.toggleSelected.bind(this);
    }



    static getDerivedStateFromProps(props, state) {
        if (props.selected !== state.propsSelected)
          return {
            selected: props.selected,
            propsSelected: props.selected
          };
      }

We always store the prevProp in state. Whenever we encounter a change in the props that are stored in the state and the props coming from the parent, we update the state part (selected) being controlled both from parent and the component itself and we also preserve the props at that point, in state for future diffing.

Usually a component which can be controlled from both the parent and itself will involve a logic of this sort. An input component found in most react component libraries is an ideal example.

like image 188
sudheer singh Avatar answered Oct 27 '22 20:10

sudheer singh