skip to Main Content

I am creating a Todo List app saving data in localstorage but when i reaload the page, my components renders twice creating empty data and replacing by the previous tasks saved in localstorage.

This is my main file: main.jsx

import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App.jsx';

import './scss/styles.scss';
import * as bootstrap from 'bootstrap';

ReactDOM.createRoot(document.getElementById('root')).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
);

Also main.jsx’s child: App.jsx

import React from 'react';
import ToDo from './Components/ToDo';

const App = () => (
  <div className="container-fluid col-md-5 mx-auto" data-bs-theme="dark">
    <h1 className="text-center fw-bold">
      To do app
    </h1>
    <ToDo />
  </div>
);

export default App;

This is App.jsx’s child: ToDo.jsx

import React, { useState, useEffect } from 'react';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faListCheck, faCirclePlus } from '@fortawesome/free-solid-svg-icons';
import SimpleToast from './Alerts/SimpleToast';
import Items from './Items';

const ToDo = () => {
  const [tasks, setTasks] = useState([]);
  const [newTask, setNewTask] = useState('');

  useEffect(() => {
    const storedTasks = localStorage.getItem('tasks');
    if (storedTasks) {
      setTasks(JSON.parse(storedTasks));
    }
  }, []);

  useEffect(() => {
    localStorage.setItem('tasks', JSON.stringify(tasks));
  }, [tasks]);

  const handleAddTask = (event) => {
    event.preventDefault();
    if (newTask.trim() === '') {
      SimpleToast.fire({
        icon: 'error',
        title: 'Enter a task first',
      });
      return;
    }
    setTasks([...tasks, { id: Date.now(), text: newTask, completed: false }]);
    setNewTask('');
  };
  const handleDeleteTask = (taskId) => {
    const updatedTasks = tasks.filter((task) => task.id !== taskId);
    setTasks(updatedTasks);
  };

  return (
    <>
      <form onSubmit={handleAddTask}>
        <div className="form-floating position-relative">
          <input
            id="new-task"
            type="text"
            className="form-control shadow-lg"
            placeholder="Enter a new task"
            value={newTask}
            onChange={(e) => setNewTask(e.target.value)}
          />
          <label htmlFor="new-task">
            Enter a new task
            {' '}
            <FontAwesomeIcon icon={faListCheck} />
          </label>
          <button
            type="submit"
            className="btn position-absolute top-50 end-0 translate-middle"
          >
            <FontAwesomeIcon
              type="submit"
              className="position-absolute translate-middle fs-3"
              icon={faCirclePlus}
            />
          </button>
        </div>
      </form>
      <Items tasks={tasks} handleDeleteTask={handleDeleteTask} />
    </>
  );
};

export default ToDo;

also the items components:

import React from 'react';
import Item from './Item';

const Items = ({ tasks, handleDeleteTask }) => (
  <ul className="list-group-flush bg-white rounded shadow-lg my-3 p-3">
    {tasks.length === 0 ? <li className="text-center list-group-item"> No tasks</li> : tasks.map((task) => (
      <Item key={task.id} task={task.text} handleDeleteTask={handleDeleteTask} idTask={task.id} />
    ))}
  </ul>
);

export default Items;

and the item component:

import React, { useState, useEffect } from 'react';
import { faPen, faTrash, faFloppyDisk } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';

const Item = ({ task, handleDeleteTask, idTask }) => {
  const [text, setText] = useState('');
  const [isEdit, setIsEdit] = useState(false);
  const [isSelected, setIsSelected] = useState(false);

  useEffect(() => {
    setText(task);
  }, [task]);

  const handleEdit = () => {
    if (!isEdit) {
      setIsEdit(true);
      return;
    }
    setIsEdit(false);
  };

  const handleCheckTask = () => {
    if (!isSelected) {
      setIsSelected(true);
      return;
    }
    setIsSelected(false);
  };

  return (
    <li className="row border rounded shadow-md p-1 my-2">
      <div className="col-1 p-0 text-center align-middle">
        <input className="form-check-input align-middle" type="checkbox" onChange={() => handleCheckTask()} />
      </div>
      <div className="col-9 p-0">
        <input className={`form-control border-0 text-dark ${isSelected ? 'text-decoration-line-through' : 'text-decoration-none'}`} value={text} type="text" onChange={(e) => setText(e.target.value)} disabled={!isEdit} />
      </div>
      <div className="col-1 p-0">
        <button type="button" className="btn" onClick={() => handleEdit()}>
          <FontAwesomeIcon className="text-danger" icon={!isEdit ? faPen : faFloppyDisk} />
        </button>

      </div>
      <div className="col-1 p-0">
        <button type="button" className="btn">
          <FontAwesomeIcon className="text-danger" icon={faTrash} onClick={() => handleDeleteTask(idTask)} />
        </button>

      </div>
    </li>
  );
};

export default Item;

I have tried logging inside Todo.jsx first useEffect console.log(storedTasks) and when I reload i can see the previous tasks in console but after that a new empty array that i think is replacing the first array.

logging storedTasks

3

Answers


  1. Try this use storedTasks.length instead of storedTask as Boolean of empty array is true.

      useEffect(() => {
        const storedTasks = localStorage.getItem('tasks');
        if (storedTasks.length) {
          setTasks(JSON.parse(storedTasks));
        }
      }, []);
    
    Login or Signup to reply.
  2. I think The following

     useEffect(() => {
        localStorage.setItem('tasks', JSON.stringify(tasks));
      }, [tasks]);
    

    might fire the first time as well as the useEffect above it, and since setTasks might not have taken effect when this code is executed, tasks would be [].

    I suggest doing localStorage.setItem directly where you are changing state like in handleAddTask. Add something like

    localStorage.setItem('tasks', JSON.stringify([...tasks, { id: Date.now(), text: newTask, completed: false }]));
    

    Note that I did not use tasks since setTasks might not have taken effect by the time the line is executed.

    Login or Signup to reply.
  3. It looks like you’re overwriting tasks with the default value every time you load. A quick an easy solution is if you change your second useEffect to run only on a valid task like for instance if there is a value in tasks or if tasks is defined.

    you will need to define your tasks as undefined

      const [tasks, setTasks] = useState(undefined);
    
      useEffect(() => {
       // here we check if it's valid before storing it
        if(tasks){
          localStorage.setItem('tasks', JSON.stringify(tasks));
        }
      }, [tasks]);
    

    rendering

    when rendering we use the logical or || to render 0 tasks when task is undefined

    <Items tasks={tasks || []} handleDeleteTask={handleDeleteTask} />
    

    summary code

    const ToDo = () => {
      const [tasks, setTasks] = useState(undefined);
      const [newTask, setNewTask] = useState('');
    
      useEffect(() => {
        const storedTasks = localStorage.getItem('tasks');
        if (storedTasks) {
          console.log(storedTasks, " set");
          setTasks(JSON.parse(storedTasks));
        }
      }, []);
    
      useEffect(() => {
        if(tasks != undefined){
          localStorage.setItem('tasks', JSON.stringify(tasks));
        }
      }, [tasks]);
    
      // ...
    
      return (
        <>
          {/* ... */}
          <Items tasks={tasks || []} handleDeleteTask={handleDeleteTask} />
        </>
      );
    };
    
    export default ToDo;
    
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search