user1756971
user1756971

Reputation: 165

Why child component doesn't get updated in components tree?

I try to pass state to the child, to update the list of objects. When I add an entry, it's not rendered in the child component. I also checked that state.contacts actually gets replaced with new array, but it didn't work.


  constructor(props) {
    this.super(props);
  }

  removeContact(event) {
    this.setState((state) => {
      state.contacts = state.contacts.filter((contact) => contact.key !== event.target.key )
      return state;
    })
  }

  render() {
    return (
      <Fragment>
        <span>{this.props.contact.name}</span>
        <span>{this.props.contact.phone}</span>
        <span>{this.props.contact.adress}</span>
        <a href="#" onClick={this.removeContact}>X</a>
      </Fragment>
    )
  }
}

class Contacts extends Component {

  constructor(props) {
    super(props);
    this.state = { contacts: props.contacts };
  }

  render() {
    console.log(this.state.contacts); // always displays empty array
    return (
      <div>
        {this.state.contacts.map((contact, index) => 
          <div>
            <Contact key={index} contact={contact} contacts={this.state.contacts}/>
          </div>
        )}
      </div>
    )
  }
}

class App extends Component {
  state = {
    time: new Date(),
    name: "",
    phone: "",
    adress: "",
    contacts: []
  }

  change = (event) => {
    let nameOfField = event.target.name;

    this.setState({[nameOfField]: event.target.value})
  }

  // click = () => { 
  //   this.setState((state) => {
  //     state.time = new Date();
  //     return state;
  //   })
  // }

  addContact = () => {
    let name = this.state.name;
    let phone = this.state.phone;
    let adress = this.state.adress;

    this.setState((state) => {
      return {contacts: [ ... state.contacts.concat([{name, adress, phone}])]}
    });

  }

  render() {
    return (
    <div className="App">
      <Timestamp time={this.state.time}/>
      <Contacts contacts={this.state.contacts}/>
      <input name="name" value={this.state.name} onChange={this.change} placeholder="Name"/>
      <input name="phone" value={this.state.phone} onChange={this.change} placeholder="Phone"/>
      <input name="adress" value={this.state.adress} onChange={this.change} placeholder="Adress"/>
      <button onClick={this.addContact}>Add contact</button>
    </div> 
    )
  }
}

ReactDOM.render(<App time={Date.now().toString()}/>, document.getElementById('root'));

Upvotes: 1

Views: 63

Answers (2)

Lu&#239;s
Lu&#239;s

Reputation: 2853

You are currently modifying the state in Contacts from it's child component Contact. You can't update a parents state directly from within a child component.

What you could do is create a removeContact function in your Contacts component and pass the entire function down to your Contact component. That way when you call removeContact in your child component, it will actually call it from the parent, modify the parents state, and update all it's children with the new state.

Upvotes: 2

Davin Tryon
Davin Tryon

Reputation: 67336

If values are passed to Components you should render them as props. There is no need to copy into the child component state:

class Contacts extends Component {

  render() {
    console.log(this.props.contacts); // use props instead of state
    return (
      <div>
        {this.props.contacts.map((contact, index) => 
          <div>
            <Contact key={index} contact={contact} contacts={this.props.contacts}/>
          </div>
        )}
      </div>
    )
  }
}

Using this.props is good practice because it allows React to deterministically render (If the same props are passed, the same render result is returned).

Upvotes: 2

Related Questions