reactjs 导入组件时如何防止双重渲染?

jutyujz0  于 2023-02-22  发布在  React
关注(0)|答案(3)|浏览(135)

我正在创建一个待办事项列表应用程序,将数据保存在本地存储中,但当我重新加载页面时,我的组件呈现两次,创建空数据并替换为保存在本地存储中的先前任务。
这是我的主文件: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>,
);

也是main.jsx的子级: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;

这是App.jsx的子级: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;

还有项目组成部分:

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;

和项目组件:

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;

我已经尝试在Todo.jsx中记录第一个useEffect console.log(storedTasks),当我重新加载时,我可以在控制台中看到以前的任务,但在那之后,我认为一个新的空数组正在替换第一个数组。

yftpprvb

yftpprvb1#

看起来你每次加载的时候都用默认值覆盖了任务。一个快速简单的解决方案是,如果你改变你的第二个useEffect只在一个有效的任务上运行,比如,如果任务中有一个值,或者如果任务被定义了。
您需要将任务定义为未定义

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]);

渲染

在渲染时,当任务未定义时,我们使用逻辑或||来渲染0任务

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

汇总代码

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;
8yparm6h

8yparm6h2#

尝试使用storedTasks.length代替storedTask,因为空数组的Booleantrue

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

gv8xihay3#

我认为以下几点

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

可能会在第一次触发,以及它上面的useEffect,并且由于setTasks在执行此代码时可能尚未生效,因此tasks将为[]
我建议直接执行localStorage.setItem,在handleAddTask中更改状态。

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

注意,我没有使用tasks,因为setTasks可能在执行该行时还没有生效。

相关问题