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.
3
Answers
Try this use
storedTasks.length
instead ofstoredTask
asBoolean
of empty array istrue
.I think The following
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 inhandleAddTask
. Add something likeNote that I did not use
tasks
since setTasks might not have taken effect by the time the line is executed.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
rendering
when rendering we use the logical or
||
to render 0 tasks when task is undefinedsummary code