Dylan Parks
Dylan Parks

Reputation: 1

React parent component reseting child components state when parent state updates

I have a parent component called ToDoLists which contains an input and allows you to input a new to do list name. This component maps over a list array in the component's state to create the different List components.

The List component allows you to add todo items. For example: Morning Routine List with breakfast, shower, shave as todo items.

I can get the ToDoLists component to create new List components. However, when I add todo items in the List component state retyping in the input of the parent component seems to erase the state of the child components.

I'm not sure if this is even the right way to attempt to do this. Is this because I need to pass prevState when I add a new list?

ToDoLists component

import React, { Component } from 'react';
import '../App.css';
import List from './list.js';

class ToDoLists extends Component {
  constructor(props){
    super(props)
    this.state = {
      term: '',
      list: []
    };
  }
  onChange = (event) => {
    this.setState({term: event.target.value});
  }
  onSubmit = (event) => {
    event.preventDefault()
    this.setState({
      term: '',
      list: [...this.state.items, this.state.term]
    })
  }

  render() {
    const generateKey = (x) => {
      return `${x}_${new Date().getTime()}`
    };

    return(
      <div>
        <h3>ToDo Lists Component</h3>
          <form onSubmit={this.onSubmit}>
            <input placeholder='Add A New List' value={this.state.term} onChange={this.onChange}/>
            <button className='btn btn-sm btn-dark'>Submit</button>
          </form>
          <br/><br/>
          {
            this.state.list.map((data) => {return(<List title={data} key={generateKey(data)}/>)})
          }

      </div>
    )
  }
}


export default ToDoLists

List component

import React, { Component } from 'react';
import '../App.css';

class List extends Component {
  constructor(props){
    super(props)
    this.state = {
      term: '',
      items: []
    };
    this.onChange = this.onChange.bind(this);
  }

  onChange = (event) => {
    this.setState({term: event.target.value});
  }
  onSubmit = (event) => {
    event.preventDefault()
    this.setState({
      term: '',
      items: [...this.state.items, this.state.term]
    })
  }

  render() {
    const generateKey = (x) => {
      return `${x}_${new Date().getTime()}`
    }

    return(
      <div>
        <h3>{this.props.title}</h3>
        <form onSubmit={this.onSubmit}>
          <input placeholder='Add New Todo Item' value={this.state.term} onChange={this.onChange}/>
          <button className='btn btn-sm btn-dark'>Submit</button>
        </form>
        <ul>
          {
            this.state.items.map((item) => {return(<li key={generateKey(item)}>{item}</li>)})
          }
        </ul>
      </div>
    )
  }
}


export default List

Upvotes: 0

Views: 168

Answers (1)

Jonas Wilms
Jonas Wilms

Reputation: 138527

The reset is caused by this little part:

 key={generateKey(data)}

Whenever you rerender the parent, the childs get new keys as generateKey is returning a nearly random key. When diffing, React tries to find out wich components were removed and which were addee, and it uses the key prop to simplify this. If a key was part of the previous children and the current ones, the element stays where it is, if the key disappeared the component will be destroyed, and if the key is new it will create a new component. So in your case, all Lists will be destroyed and new ones will be created whenever ToDoLists.render() will run. To change that, just make key persistent:

 const generateKey = (x) => {
  return "" + x;
}

(If two lists might have the same name it might be a good idea to take the Lists index as key.)


While that works, it is actually hard to store the state now, as it is split up into multiple components. Therefore you might want to "lift state up" so that all lists are managed in the parent, and every change gets propagated up to that parent:

  class ToDoLists extends React.Component {
    constructor(props) {
      super(props);
      this.state = { lists: [] };
   }

   updateList(old, updated) {
    this.setState(({ lists }) => ({ lists: lists.map(l => l === old ? updated : l) }));
   }

   render() { return this.state.lists.map(list => <List list={list} onChange={updated => this.updateList(list, updated)} />); }
}

class List extends React.Component {
  onSubmit() {
    const { onChange, list } = this.props;
    this.setState({ term: "" });
    onChange([...list, this.state.term]);
  }
  //...
}

Upvotes: 0

Related Questions