You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React Strict Mode下localStorage数组值刷新后无法持久化

React Strict Mode下localStorage数据无法持久化的解决方案

问题场景

开发的React应用包含Modal、Task、Navbar等组件,通过状态与函数(如adder)实现组件间通信。核心问题是:页面刷新后localStorage中的任务数组无法持久化,但移除React Strict Mode后功能恢复正常。相关AppBody组件代码如下:

import React from 'react'
import Modal from './Modal'
import 'bootstrap/dist/css/bootstrap.min.css';
import Task from './Task';
import { useState, useEffect } from 'react'
export default function AppBody() {
    const [showModel, setVisibility] = useState(false);
    const [taskList, updateTaskList] = useState([]);
    const [current, setCurrent] = useState("");
    const openModal = () => {
        setVisibility(true);
    }
    const closeModal = () => {
        setVisibility(false);
    }
    const handleOnAdd = () => {
        const obj = {
            id: taskList.length + 1,
            title: current,
            isComplete: false
        }
        updateTaskList([...taskList, obj]);
        setVisibility(false);
        console.log('Task list updated locally');
    }
    useEffect(() => {
        try {
            const storedTasks = localStorage.getItem("taskList");
            console.log("for first render only");
            if (storedTasks) {
                updateTaskList(JSON.parse(storedTasks));
            } else {
                console.log("No tasks found in local storage");
            }
        } catch (error) {
            console.error("Error retrieving data from local storage:", error);
        }
    }, []);

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

    const handleStatusChange = (id, status) => {
        const updatedTasks = taskList.map(task => {
            if (task.id === id) {
                return { ...task, isComplete: status };
            }
            return task;
        });
        updateTaskList(updatedTasks);
    }
    return (
        <>
            <div className="add" onClick={openModal}><img src="./src/assets/icons8-plus.svg" alt="" /></div>
            <Modal adder={handleOnAdd} setCurrent={setCurrent} visibility={showModel} close={closeModal} />
            <div className='cardsContainer'>
                {
                    taskList.map((item) => {
                        return <Task handleStatusChange={handleStatusChange} key={item.id} taskNo={item.id} current={item.title} status={item.isComplete} />
                    })
                }
            </div>
        </>
    )
}

问题原因

React Strict Mode在开发环境下会双重执行组件的初始化逻辑、副作用(useEffect)等操作,用于检测潜在代码问题。当前代码的问题链:

  1. 组件首次初始化:taskList初始为空数组,空依赖useEffect读取localStorage并更新taskList,随后依赖taskList的useEffect将有效数据存入localStorage。
  2. Strict Mode触发第二次初始化:taskList再次设为空数组,空依赖useEffect再次读取localStorage并更新taskList。
  3. React为检测副作用稳定性,会撤销这次状态更新,taskList变回空数组,依赖taskList的useEffect再次触发,将空数组存入localStorage,覆盖之前的有效数据。

最终导致页面刷新后localStorage只剩空数组,数据无法持久化。

修复方案

核心修改:将localStorage读取逻辑移到useState初始化

直接在useState的工厂函数中读取localStorage数据,代替空依赖的useEffect,避免Strict Mode双重执行的影响。同时优化状态更新方式,解决闭包和ID重复问题。

修复后的完整代码

import React from 'react'
import Modal from './Modal'
import 'bootstrap/dist/css/bootstrap.min.css';
import Task from './Task';
import { useState, useEffect } from 'react'

export default function AppBody() {
    const [showModel, setVisibility] = useState(false);
    // 初始化时直接从localStorage读取数据,规避Strict Mode双重执行的影响
    const [taskList, updateTaskList] = useState(() => {
        try {
            const storedTasks = localStorage.getItem("taskList");
            return storedTasks ? JSON.parse(storedTasks) : [];
        } catch (error) {
            console.error("Error retrieving data from local storage:", error);
            return [];
        }
    });
    const [current, setCurrent] = useState("");

    const openModal = () => {
        setVisibility(true);
    }

    const closeModal = () => {
        setVisibility(false);
    }

    const handleOnAdd = () => {
        // 用Date.now生成唯一ID,避免删除任务后ID重复
        const obj = {
            id: Date.now(),
            title: current,
            isComplete: false
        }
        // 函数式更新,确保基于最新状态修改
        updateTaskList(prev => [...prev, obj]);
        setVisibility(false);
        setCurrent(""); // 添加后清空输入框
        console.log('Task list updated locally');
    }

    // 仅保留存储数据的副作用
    useEffect(() => {
        localStorage.setItem('taskList', JSON.stringify(taskList));
    }, [taskList]);

    const handleStatusChange = (id, status) => {
        // 函数式更新状态
        updateTaskList(prev => prev.map(task => {
            if (task.id === id) {
                return { ...task, isComplete: status };
            }
            return task;
        }));
    }

    return (
        <>
            <div className="add" onClick={openModal}>
                <img src="./src/assets/icons8-plus.svg" alt="添加任务" />
            </div>
            <Modal adder={handleOnAdd} setCurrent={setCurrent} visibility={showModel} close={closeModal} />
            <div className='cardsContainer'>
                {taskList.map((item) => (
                    <Task 
                        handleStatusChange={handleStatusChange} 
                        key={item.id} 
                        taskNo={item.id} 
                        current={item.title} 
                        status={item.isComplete} 
                    />
                ))}
            </div>
        </>
    )
}

关键优化点说明

  1. 初始化逻辑移至useState:组件挂载时直接读取localStorage作为初始状态,绕过Strict Mode对useEffect的双重执行,从根源避免数据被覆盖。
  2. 唯一ID生成:替换taskList.length +1为Date.now(),解决删除任务后新增任务ID重复的问题。
  3. 函数式状态更新:使用prev => [...prev, obj]方式更新状态,确保每次修改基于最新状态值,避免闭包导致的状态不一致。
  4. 用户体验优化:添加任务后自动清空输入框,提升交互流畅度。

内容的提问来源于stack exchange,提问作者Sahil Sadekar

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.29 05:55:56