Update state in class from const

Viewed 275

I'm new to React JS and I'm coding a really simple task manager. So, I have all tasks in state element of MyTodoList class (each task has: id, name, description, completed). Then I draw each task separately with Task constant.

I want to implement changing buttons below every task (if task is completed button should be "Done", if not - "Not done").

I do not understand how I can update "completed" attribute (which is in MyTodoList class in state) from const Task.

Would be grateful for any hint!

Code:

import logo from './logo.svg';
import './App.css';
import React from 'react';

function DoneButton({onClick}) {
  return (
    <button onClick={onClick}>
      Done
    </button>
  );
}

function NotDoneButton({onClick}) {
  return (
    <button onClick={onClick}>
      Not done
    </button>
  );
}


const Task = ({id, name,  description, completed}) => {
    const handleDoneClick = () => {
      completed= false //something different should be here
    }

    const handleNotDoneClick = () => {
      completed= true //something different should be here
    }

    let button;
    if (completed) {
      button = <DoneButton onClick={handleDoneClick} />
    } else {
      button = <NotDoneButton onClick={handleNotDoneClick} />
    } 
  
    return (
      <div className='task'>
        <h3>{name}</h3>
        <div>{description}</div>
        <div>{completed}</div>
        {button}
      </div>
    )
  }


class MyTodoList extends React.Component {
  state = {
    tasks: [
      {
        id: 1,
        name: 'Walk the dog',
        description: 'Have to walk the dog today',
        completed: false,
      },

    ],

  }

  

  render () {
    return(
      <div>
        <header><h1>TO-DO</h1></header>
        <div>{this.state.tasks.map(task => <Task id={task.id} name={task.name} 
        description={task.description} completed={task.completed}/>)}
        </div>

      </div>
      )
        }

}

const App = () => {
  return (
    <MyTodoList />
  )
}

export default App;
1 Answers

You should never re-assign parameters unless it is the only solution you have, but you should definitely never re-assign parameters which you plan to depend on in the render method.

The proper solution would be this:

import React, { useState } from 'react';

...

const Task = ({ id, name, description, completed }) => {
  const [isCompleted, setIsCompleted] = useState(completed);

  const handleDoneClick = () => {
    setIsCompleted(true);
  };

  const handleNotDoneClick = () => {
    setIsCompleted(false);
  };

  let button;
  if (isCompleted) {
    button = <DoneButton onClick={handleDoneClick} />;
  } else {
    button = <NotDoneButton onClick={handleNotDoneClick} />;
  }

  return (
    <div className="task">
      <h3>{name}</h3>
      <div>{description}</div>
      <div>{isCompleted}</div>
      {button}
    </div>
  );
};

You need to use local state, in which you will set the initial value (completed, or not completed) which you are receiving from props, and then change the state, and not the parameter. Furthermore, continue using the state value of your completed (isCompleted) so React will react to its change.

This is not the final solution though, as this will only keep the local change of the task, and not change the task status in tasks list.

Basically, if you component A holds the tasks and their complete status, you need to create a method in the component A which will modify the respective task by ID, to the correct status. Then you need to pass the respective method to component B which will call the method and pass along the id and complete status (true / false) The method which is assigned in component A will then look through the list of tasks, find the proper task by ID, and assign its new completed value you passed from component B. After that, react does its thing and automatically updates completed prop you passed to component B

Working snippet:

function DoneButton({ onClick }) {
  return <button onClick={onClick}>Done</button>;
}

function NotDoneButton({ onClick }) {
  return <button onClick={onClick}>Not done</button>;
}

const Task = ({ id, name, description, completed, onTaskClick }) => {
  const handleDoneClick = () => {
    onTaskClick(id, false);
  };

  const handleNotDoneClick = () => {
    onTaskClick(id, true);
  };

  let button;
  if (completed) {
    button = <DoneButton onClick={handleDoneClick} />;
  } else {
    button = <NotDoneButton onClick={handleNotDoneClick} />;
  }

  return (
    <div className="task">
      <h3>{name}</h3>
      <div>{description}</div>
      <div>{completed}</div>
      {button}
    </div>
  );
};

const MyTodoList = () => {
  const [tasks, setTasks] = React.useState([
    {
      id: 1,
      name: 'Walk the dog',
      description: 'Have to walk the dog today',
      completed: false,
    }
  ]);

  const onTaskClick = React.useCallback(
    (id, isCompleted) => {
      const updatedTasks = [...tasks].map((task) => {
        if (task.id === id) {
          return {
            ...task,
            completed: isCompleted,
          };
        }

        return task;
      });

      setTasks(updatedTasks);
    },
    [tasks]
  );

  return (
    <div>
      <header>
        <h1>TO-DO</h1>
      </header>
      <div>
        {tasks.map((task) => (
          <Task onTaskClick={onTaskClick} id={task.id} name={task.name} description={task.description} completed={task.completed} />
        ))}
      </div>
    </div>
  );
};

const App = () => <MyTodoList />;

ReactDOM.render(<App />, document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>

Related