Reputation: 1
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
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 List
s 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