0

I'm starting to learn React with a to-do list mini project. It has three Components:

  • Tasks is the main component (child of App). It render a NewTask component which add new tasks and contains an array with all of the Task components to show.

  • NewTask is an input to add a new Task to the list.

  • Task represent a task

My goal is to add a new Task into the top of the list, so I have thought that the best way to do this is to add the Task to the start of the array of Tasks. Here is the problem, when I do this the render doesn't display the tasks correctly, and repeat the last one. When I add the task to the last element the render results are correct. Here some pictures:

enter image description here enter image description here

(At the left not correct and at the right correct)

Here is the code of the three components:

class Tasks extends React.Component{

    constructor(props){
        super(props);

        this.state = {
           tasks: [<Task text="Comprar cerveza"/>, 
                   <Task text="Hacer la comida"/>, 
                   <Task text="Ver HIMYM"/>,
                   <Task text="Escuchar nuevo disco"/>]
        }
    }

    addNewTask = (text) => {
        // this.setState({tasks: this.state.tasks.concat(<Task text={text}/>)}) // Add to bottom 
        this.setState({tasks: [<Task text={text}/>].concat(this.state.tasks)}) // Add to top
    }

    render(){
        return (
            <div className="task-container">
                <NewTask parentMethod={this.addNewTask} text="New Task"/>
                {this.state.tasks}
            </div>
        );
    }
}
class NewTask extends React.Component{
    constructor(props){
        super(props);
    }

    handleKeyPress = (event) => {
        if(event.key === 'Enter'){
            this.props.parentMethod(event.target.value);
        }
    }

    render(){
        return (
            <div className="task">
                <input className="new-task" placeholder={this.props.text} onKeyPress={this.handleKeyPress}/>
            </div>
        );
    }
}
class Task extends React.Component{
    constructor(props){
        super(props);

        this.state = {
           completed: false
        }
    }

    changeState(){
        this.setState({completed: !this.state.completed});
    }

    render(){
        let class_button = "check-button " + (this.state.completed ? 'completed' : 'not-completed');
        let class_text = "posted-task " + (this.state.completed ? 'completed' : 'not-completed');

        return (
            <div className="task">
                <button className={class_button} onClick={this.changeState.bind(this)}/>
                <input className={class_text} defaultValue={this.props.text}/>
            </div>
        );
    }
}

2 Answers 2

1

Try this instead to add to the top:

addNewTask = (text) => {
            this.setState(prevstate => {tasks: [<Task text={text}/>, ...prevState.tasks]
        }

this is a less error prone of updating an array in the state.

Also its not really a good idea to have something like this:

this.state = {
           tasks: [<Task text="Comprar cerveza"/>, 
                   <Task text="Hacer la comida"/>, 
                   <Task text="Ver HIMYM"/>,
                   <Task text="Escuchar nuevo disco"/>]
}

What you should have is something like this:

    this.state = {
               tasks: ["Comprar cerveza", "Hacer la comida", "Ver 
    HIMYM","Escuchar nuevo disco"] 
}

And you would render that like this:

{this.state.tasks.map((task, index) => <Task text={task} key={`${index}.${task}`}/>}
Sign up to request clarification or add additional context in comments.

2 Comments

That's not work for me. Could you explain the statement?
I noticed that I had a warning about the keys, so I assign a unique key to each element as you did. I didn't know nothing about keys when I posted the question. Thanks!
0

The error doesn't come by the way how I added the new element to the array. The problem was that if I don't specify a unique key for each element, React detect it as the same component and it won't be rendered again. The new component looks like:

class Tasks extends React.Component{

    constructor(props){
        super(props);

        this.state = {
            id: [0,1,2,3],
            tasks: ["Comprar cerveza", 
                    "Hacer la comida", 
                    "Ver HIMYM",
                    "Escuchar nuevo disco"]
        }
    }

    addNewTask = (text) => {
        // Add to top
        this.setState({ id: [this.state.id.length].concat(this.state.id),
                        tasks: [text].concat(this.state.tasks)})
    }

    render(){
        return (
            <div className="task-container">
                <NewTask parentMethod={this.addNewTask} text="New Task"/>
                {/* Create the task */}
                {this.state.tasks.map( (el,i) => {
                    return(
                        <Task id={this.state.id[i]} text={el}/>
                    );
                })}
            </div>
        );
    }
}
class Task extends React.Component{
    constructor(props){
        super(props);

        this.state = {
           completed: false
        }
    }

    changeState(){
        this.setState({completed: !this.state.completed});
    }

    render(){
        let class_button = "check-button " + (this.state.completed ? 'completed' : 'not-completed');
        let class_text = "posted-task " + (this.state.completed ? 'completed' : 'not-completed');

        return (
            <div className="task" key={this.props.id}>
                <button className={class_button} onClick={this.changeState.bind(this)}/>
                <input className={class_text} defaultValue={this.props.text}/>
            </div>
        );
    }
}

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.