How to prevent double rendering when importing my components?

Advertisements

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', "[]");

Leave a ReplyCancel reply