Added React child component doesn't receive updated value

Viewed 52

I'm attempting to add a child to my react app via button and have it's value get updated via button click. When I click the button the 'hard coded' child gets updated but the child added via button isn't being updated. I'm missing something fundamental.

I tried passing props to the child assuming state updates would propagate but it isn't working. https://codepen.io/esoteric43/pen/YzLqQOb


class App extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      text: "Starting",
      children: []
    };
  }

  handleClick() {
    this.setState({ text: "Boom" });
  }

  handleAdd() {
    this.setState({
      children: this.state.children.concat(
        <Child1 key={1} text={this.state.text}></Child1>
      )
    });
  }

  render() {
    return (
      <div>
        <Child1 text={this.state.text}></Child1>

        {this.state.children}

        <button onClick={() => this.handleClick()}>Change</button>

        <button onClick={() => this.handleAdd()}>Add</button>
      </div>
    );
  }
}

class Child1 extends React.Component {
  render() {
    return <div>{this.props.text}</div>;
  }
}


const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
    <App />
);

To reproduce in the above codepen link, click add then click change. The added element is not updated. Both children should be updated when the change button is clicked.

3 Answers

You have to map the previous state values as well in handleClick method:

handleClick() {
    this.setState((prevState) => ({
      ...prevState,
      children:
        prevState.children.length > 1
          ? [
              ...prevState.children
                .slice(0, prevState.children.length - 1)
                .map((_) => "Boom"),
              "Boom"
            ]
          : ["Boom"]
    }));
  }

Check it working on this CodeSandbox.

You need to update the last item in children. Your current solution updates the initial text.

Try like this:

class App extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      text: "Starting",
      children: ["Starting"]
    };
  }

  handleClick() {
    this.setState((prevState) => ({
      ...prevState,
      children:
        prevState.children.length > 1
          ? [
              ...prevState.children.slice(0, prevState.children.length - 1),
              "Boom"
            ]
          : ["Boom"]
    }));
  }

  handleAdd() {
    this.setState({
      children: this.state.children.concat(
        <Child1 key={1} text={this.state.text}></Child1>
      )
    });
  }

  render() {

    return (
      <div>
        {this.state.children.map((child, index) => (
          <div key={index}>{child}</div>
        ))}
        <button onClick={() => this.handleClick()}>Change</button>
        <button onClick={() => this.handleAdd()}>Add</button>
      </div>
    );
  }
}

class Child1 extends React.Component {
  render() {
    return <div>{this.props.text}</div>;
  }
}

ReactDOM.render(<App />, document.querySelector('.react'));
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div class='react'></div>

Do you mean something like this?

class App extends React.Component {
   constructor() {
      super()
      this.state = {
         children: []
      }
   }
   
   handleAddClick = ()=>{
       this.setState(prev=>({children:[...prev.children, `child ${this.state.children.length+1}`]}))
   }
   
   handleChangeClick=(item)=> {
       let draft = [...this.state.children];
       draft[draft.findIndex(i=>i===item)]='boom';
       this.setState({children: draft});
   }
   render() {
      return(
        <div>
          <button onClick={this.handleAddClick}>Add child</button>
          {
             this.state.children.map(child=>(<Child onChange={this.handleChangeClick} title={child}/>))
          }
        </div>
      )
   }
}


class Child extends React.Component {
   render() {
      return <div>
         {this.props.title}
         <button onClick={()=>this.props.onChange(this.props.title)}>change me</button>
      </div>
   }
}

ReactDOM.render(
    <App />,
    document.getElementById("root")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"/>

Related