7

I am making a simple to-do list app in React. I have 3 states, inputText (the task the user enters), triggerAnimation(to trigger animations), and tasks (the list of tasks user has entered). However I don't know how to update the tasks state (which is an array) to push the new tasks. Here is the code.

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

class App extends Component {

  constructor(props) {
    super(props);

    this.state = {
      inputText: '',
      triggerAnimation: '',
      tasks: []
    }
  }

//The function triggered by button which sends the task the user has entered to the tasks array state:

  addItem() {
    document.querySelector("#textfield1").value = ""
    this.setState({ 
      triggerAnimation: 'fadein', tasks: 
      this.state.inputText 
    }) 
  }



  render() {
    //Where User enters task:
    return (
      <div className="App">
        <main>
          <div className="enterTask">
            <input type="text" className="inputclass" id="textfield1" 
              placeholder='Enter a task.' 
              onChange={event => this.setState({ 
                inputText: event.target.value })} 
              onKeyPress={event => {
                if(event.key === 'Enter') {
                  this.addItem();
                }
               }} 
             />
            <br />
            <br />
            <button className="button" 
              onClick={() => this.addItem()} data-
                toggle='fadein' data-target='list'>+
            </button>
         </div>


    <!-- Where tasks will appear: -->

         <div className="log">
           <p className='list'>
             <span class={this.state.triggerAnimation}>  
               {this.state.tasks}
             </span>
           </p>
           <button className="button">-</button>
         </div>
       </main>
     </div>
    )
  }
}  

export default App;
2
  • Please fix the formattig. Thats not readable at all Commented Feb 14, 2018 at 20:42
  • 1
    Your addItem method is resetting the state of tasks as a string. You can use the spread operator. this.setState({tasks: [...this.state.task, this.state.inputText]}) Commented Feb 14, 2018 at 20:50

6 Answers 6

9

However I don't know how to update the tasks state (which is an array) to push the new tasks.

Probably the cleanest way to "push to an array" in state is to use ES6 array spread. The best practice would also be to use the setState callback syntax to ensure the correct state is committed before you push the new task:

this.setState(prevState => ({
  tasks: [...prevState.tasks, newTask] 
}));
Sign up to request clarification or add additional context in comments.

Comments

4

Seems like what you want is this..

addItem() {
document.querySelector("#textfield1").value = ""
this.setState({
            triggerAnimation: 'fadein',
            tasks: this.state.tasks.concat(this.state.inputText)}) 
}

4 Comments

How can I get the entire array to show in the html paragraph element?
<p>{this.state.tasks}</p> ? or if you want each one in an individual paragraph tag {this.state.tasks.map((task, i) => <p key={i}> {task} </p>} (in markup)
Hi Yes I'd like to get each in a individual tag, however I think your syntax is a little off because I'm getting an error at the ending bracket. EDIT: I have fixed the problem, missing parentheses.
Never use setState() with input from this.state, as this.state may be outdated. Use the callback form when the state update depends previous state: setState(prevState => ({ triggerAnimation: 'fadein', tasks: prevState.tasks.concat(prevState.inputText) }))
3

You can use .concat method to create copy of your array with new data:

  addTask() {
    this.setState({tasks: this.state.tasks.concat(["new value"])})
  }

You also need to bind this to addTask in your constructor:

this.addTask = this.addTask.bind(this)

See my example:

https://jsfiddle.net/69z2wepo/103069/

Documentation: https://reactjs.org/docs/react-component.html#setstate

3 Comments

.concat expects an Array as the argument. You should change it to: this.state.tasks.concat(["new value"])
I get the error Cannot read property 'bind' of undefined when adding this.addTask = this.addTask.bind(this) to my constructor.
@Usman :) It's because addTask is not defined. You have a function called addItem. I used addTask. Try binding to the function that adds your items.
1

try this

import React from 'react';

class Todo extends React.Component {
  constructor(props) {
    super();
    this.state = {
      value: '',
      items: []
    }
  }

  onChange = e => this.setState({ value: e.target.value })

  onEnter = e => {
    if(e.charCode !== 13) return;
    this.addItem();
  };

  onClick = e => {
    this.addItem()
  };

  addItem = () => {
    const { value } = this.state;
    if(!!value.trim()) return;
    this.setState(prev => ({ items: [...prev.items, value], value: '' }))
  };

  render() {
    const { value } = this.state
    return (
      <div>
        <div>
          <input
            type="text"
            value={value}
            name="abc"
            onChange={this.onChange}
            onKeyPress={this.onEnter}
          />
        </div>
        <button onClick={this.onClick}>Add</button>
      </div>
    )
  }
}

Comments

1

FTFY better to just use comments in the code, regarding the problem(s) you want to get the tasks array then can concat the stuff to get a new array.

setState({tasks:this.state.tasks.concat([this.state.inputText])})

Wouldn't hurt to clean up the code some too... learning react myself the book "the road to learning react" has some good tips on how to set things up to be a bit more readable.


Edit actually put the right code here now...

9 Comments

@MarioTacke yes it does? concat will create a new array attaching the elements from the original array to the array provided as an argument... not sure what is missing?
You should never mutate state directly. you are mutating the state value inside ref callback method to store the node ref.
@AlexFallenstedt .concat doesn't modify the original array it returns a new array... guess to be more complete I could add the setState part?
Not sure why everyone wants to hate this answer but you're the third to come by and down vote or leave a review mark on this without giving a clear reason why...
@shaunhausin While it is technically possible to alter state by writing to this.state directly, it will not lead to the Component re-rendering with new data, and generally lead to state inconsistency. The fact that setState causes reconciliation(the process of re-rendering the components tree) is base of the next property — setState is asynchronous. This allows us to have multiple calls to setState in a single scope and not trigger not needed re-renders of the whole tree.
|
0

With react, you're almost always going to have to store form field information in state (controlled components) so, how about turning todo task input field into a controlled component, like so:

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

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

        this.state = {
            inputText: '',
            triggerAnimation: '',
            tasks: []
        }

        this.onInputChange = this.onInputChange.bind(this);
        this.onInputKeyPress = this.onInputKeyPress.bind(this);
        this.addItem = this.addItem.bind(this);
    }

    onInputChange(e) {
        this.setState({ inputText: e.target.value });
    }

    onInputKeyPress(e) {
        if (e.key === "Enter") {
            this.addItem();
        }
    }

    addItem() {
        const itemToAdd = this.state.inputText;
        const tasks = this.state.tasks;
        this.setState({
            inputText: "",
            tasks: tasks.concat(itemToAdd);
        });
    }

    render() {
        const { inputText } = this.state;
        return(
            <div>
                <input type="text" className="inputclass" id="textfield1" placeholder='Enter a task.' 
                   value={inputText} onChange={this.onInputChange} onKeyPress={this.onInputKeyPress} />
                <br />
                <br />
                <button className="button" onClick={this.addItem} data-
                toggle='fadein' data-target='list'>+</button>
            </div>
        );
    }
}

Notice how input state is controlled via component state

Comments

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.