0

as in title. I am new in the react and I write simple todoApp. My App.js:

const App = () => {

const initTasks = [  
  {id: 1,text: 'Task1'},
  {id: 2,text: "Task2"},
  {id: 3,text: "Task3"}]
const [tasks, setTasks] = useState(initTasks);

  const deleteTask = (index) =>
  {
    let cp =tasks.filter(x=>x.id !== index);
    setTasks(cp);
    console.log(tasks);
  };


  const addTask =(text) => 
  {
    let newTask ={id:tasks.length+1,text:text};
    setTasks([...tasks,newTask]);
  }

  return (
    <Router>
    <div className='container'>
      <Header title='Titlee'/>
      <AddTasks addTask={addTask}></AddTasks>
      <Routes>
      <Route path='/' element=
      {
        <>
        {tasks.length > 0 ? (
                  <Tasks
                    tasks={tasks}
                    onDelete={deleteTask}
                    toggle={toggleReminder}
                  />
                ) : (
                  'No Tasks To Show'
                )
      }
          </>
        }></Route>
      <Route path='/about' element={<About />} ></Route>   
      </Routes>       
     <Footer></Footer>           
    </div>
    </Router>
  )
}

export default App;

My Tasks:

const Tasks =({tasks, onDelete, toggle}) => {
    return (
        tasks.map((task) => (
            <Task key={task.id} task={task} onDelete={onDelete} toggle={toggle}/>
          ))
      )
}

export default Tasks

and my Task.js

const Task = ({ task, onDelete,toggle }) => {
  return (
    <div className='task' onClick={()=>toggle(task.id)} key={task.id}>
      <h3>{task.text} 
      <FaTimes 
        style={{color: 'red', cursor: 'pointer'}} 
        onClick={()=>onDelete(task.id)}/>
        </h3>
      <p>{task.id}</p>
    </div>
  )
}

export default Task

I have init state with 3 hardcoded tasks in App.js. Adding new tasks works proper, and tasks are succesfully updated. The problem is with deleteTask - in 'cp' collection I have updated list of tasks, but console.log (fired just after setTasks) is shows not updated collection. Why? What is improperly done, and how to explain this bug? Moreover - lists of my tasks are not updated (in html) - why? Regards

EDIT: It doesn't matter how I initialize array with tasks. Deleting doesn't work even on case with empty array at the begining

The Trainer
  • 633
  • 1
  • 3
  • 19
  • Does this answer your question? [The useState set method is not reflecting a change immediately](https://stackoverflow.com/questions/54069253/the-usestate-set-method-is-not-reflecting-a-change-immediately) – Konrad Nov 27 '22 at 21:30
  • One thing missing from your code, which others here have provided in their own code snippets, is a call to `useState`. Where and how exactly are `tasks` and `setTasks` being initialized in your code? Could you update your question to provide it, because that’s the only reason I can think of as to why your DOM wouldn’t be updating properly. – user3781737 Nov 28 '22 at 03:06
  • @user3781737 Yes ofc, I have edited question. – The Trainer Nov 28 '22 at 21:29

2 Answers2

2

Because setTasks updates the state value in a newly rendered version of the component, it is a normal behavior that the console.log(tasks) in the same block does not receive an updated copy of tasks.

On a side note, a more standard way of setting state based on a previous value could be:

const deleteTask = (index) =>
  setTasks((prev) => prev.filter((x) => x.id !== index));

Moreover, the current addTask could possibly create a conflict between id and index, and it could be safer like this:

const addTask = (text) =>
  setTasks((prev) => {
    if (!Array.isArray(prev) || prev.length === 0) return [{ id: 1, text }];
    return [...prev, { id: prev[prev.length - 1].id + 1, text }];
  });

Here is an over simplified example so that it might be easier to visualize. It runs in the below snippet for convenience.

Hopefully it will help.

const Task = ({ task, onDelete }) => {
  return (
    <div className="task" key={task.id}>
      <h3>
        {task.text}
        <button
          style={{ color: "red", cursor: "pointer" }}
          onClick={() => onDelete(task.id)}
        >
          delete
        </button>
      </h3>
    </div>
  );
};

const Tasks = ({ tasks, onDelete }) => {
  return tasks.map((task) => (
    <Task key={task.id} task={task} onDelete={onDelete} />
  ));
};

const AddTasks = ({ addTask }) => {
  const inputRef = React.useRef(null);
  const handleSubmit = (e) => {
    e.preventDefault();
    const { value } = inputRef.current;
    if (!value) return;
    addTask(value);
  };
  return (
    <form className="add" onSubmit={handleSubmit}>
      <input type="text" ref={inputRef} />
      <button type="submit">Add Task</button>
    </form>
  );
};

const App = () => {
  const [tasks, setTasks] = React.useState([
    { id: 1, text: "Task 1" },
    { id: 2, text: "Task 2" },
    { id: 3, text: "Task 3" },
  ]);

  const deleteTask = (index) =>
    setTasks((prev) => prev.filter((x) => x.id !== index));

  const addTask = (text) =>
    setTasks((prev) => {
      if (!Array.isArray(prev) || prev.length === 0) return [{ id: 1, text }];
      return [...prev, { id: prev[prev.length - 1].id + 1, text }];
    });

  return (
    <div className="app">
      <div className="container">
        <AddTasks addTask={addTask}></AddTasks>
        {tasks.length > 0 ? (
          <Tasks tasks={tasks} onDelete={deleteTask} />
        ) : (
          "No Tasks To Show"
        )}
      </div>
    </div>
  );
};

ReactDOM.render(<App />, document.querySelector("#root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.production.min.js"></script>
John Li
  • 6,976
  • 3
  • 3
  • 27
  • The `set` functions coming back from `useState` are not asynchronous. – possum Nov 27 '22 at 20:50
  • Solution you proposed is just like mine. I took array which lambda returns to variable just for debug purpose. Second thing - why list of tasks (in html) is not updated during deleting, but it's updating in add function? – The Trainer Nov 27 '22 at 20:53
  • No, it's not like yours, you must use the prev when you use a setState, or else you'll run into sync issues. – mttetc Nov 27 '22 at 21:45
  • Do not why, but your solution doesn't work in my project, but works on the snippet. Ofc, +1 for it. – The Trainer Nov 27 '22 at 22:45
-1

React setState is async (read more here) so when you log your state just after setState, it shows the previous data not the updated one because it takes some time for the state to get updated.

You can see that your state is updated by writing an useEffect and log your state there:

const deleteTask = (index) =>
{
  const cp = tasks.filter(x=> x.id !== index);
  setTasks(cp);
};

useEffect(() => {
  console.log(tasks);
}, [tasks]);
Amirhossein
  • 1,819
  • 1
  • 6
  • 10
  • 1
    It's not about time, it's about render cycle. Const variables can't be updated. You can put `console.log` in the body of the component instead of useEffect – Konrad Nov 27 '22 at 21:32
  • By time I mean the duration that is needed for the developer to get the new data from the state. As you said it's about render cycle. So again it takes some time for the component to get updated. And also I haven't updated any const variable. What do you mean by that ? Just another point that logging data outside of the useEffect is not good because you don't have control over that. – Amirhossein Nov 28 '22 at 07:56
  • *logging data outside of the useEffect is not good because you don't have control over that.* - I don't get it – Konrad Nov 28 '22 at 07:59
  • 1
    I mean when we write our console.log inside useEffect, in the console it only logs when the dependency is updated but outside of that, it logs if any state or prop gets updated. – Amirhossein Nov 28 '22 at 08:01