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.
>Solution :
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.
useEffect(() => {
if(tasks.length > 0){
localStorage.setItem('tasks', JSON.stringify(tasks));
}
}, [tasks]);
update: i’m reading over this and i missed the case were you want to clear out all tasks. in that case you should do.
localStorage.setItem('tasks', "[]");