2

I am trying to change an element of an array using the handleToggle method, but an error occurs, what am I doing wrong, and why should I always return a new array in the React?

Child component:

    function Todolist(props) {
      const todoItem = props.todos.map((todo, index) =>
        <ListItem key={todo.id} dense button>
          <ListItemIcon>
            <Checkbox checked={todo.completed} onChange={props.onChange(todo.id)} edge="start"/>
          </ListItemIcon>
          <ListItemText primary={todo.title} />
          <ListItemSecondaryAction>
            <IconButton edge="end" aria-label="comments"></IconButton>
          </ListItemSecondaryAction>
        </ListItem>
      )
      return (
        <div>
          {todoItem}
        </div>
      )
    }

Parent component:

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      value: '',
      todos: [
        {
          title: 'Learn React',
          id: Math.random(),
          completed: true
        }
      ]
    }
  };

  handleChange = (evt) => {
    this.setState({
      value: evt.target.value
    })
  };

  handleSubmit = (evt) => {
    evt.preventDefault();

    const todos = [...this.state.todos];

    todos.push({
      title: this.state.value,
      id: Math.random(),
      completed: false
    });

    this.setState(state => ({
      todos,
      value: ''
    }))
  };

  handleToggle = (id) => {
    const todos = [...this.state.todos];

    todos.map(todo => {
      if (todo.id === id) {
        return todo.completed = !todo.completed
      } else {
        return todo
      }
    });

    this.setState(state => ({
      todos
    }))
  };

  render() {
    return (
      <div className="App">
        <Grid className="Header" justify="center" container>
          <Grid item xs={11}>
            <h1 className="Title">My to-do react app</h1>
            <FormBox value={this.state.value} onSubmit={this.handleSubmit} onChange={this.handleChange}/>
          </Grid>
        </Grid>
        <TodoList todos={this.state.todos} onChange={this.handleToggle} />
      </div>
    );
  }
}

Text of error:

Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.

1 Answer 1

4

You are executing onChange immediately

onChange={props.onChange(todo.id)}

Wrap the callback in an arrow function

onChange={() => props.onChange(todo.id)}

Also your handleToggle isn't changing the values correctly (mutating), try like this

handleToggle = (id) => {
    const todos = [...this.state.todos];

    this.setState({ todos : todos.map(todo => ({
         ...todo, 
         completed : todo.id === id ? !todo.completed : todo.completed
     }) }))
  }
Sign up to request clarification or add additional context in comments.

5 Comments

map returns a new array so he should also collect the result into and array before setting it to state.
Hey man I think he wants to change only the todo with the specified id to completed
The error was because I forgot to wrap the returned object in ( ). and yes (also missed that), I'm updating the answer
Thank you, my mistake was in callback in an arrow function
You're welcome!! You are mutating the state by doing todo.completed = !todo.completed, try using the spread operator instead. But your code isn't wrong. Just anti pattern

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.