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)等操作,用于检测潜在代码问题。当前代码的问题链:
- 组件首次初始化:
taskList初始为空数组,空依赖useEffect读取localStorage并更新taskList,随后依赖taskList的useEffect将有效数据存入localStorage。 - Strict Mode触发第二次初始化:
taskList再次设为空数组,空依赖useEffect再次读取localStorage并更新taskList。 - 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> </> ) }
关键优化点说明
- 初始化逻辑移至useState:组件挂载时直接读取localStorage作为初始状态,绕过Strict Mode对useEffect的双重执行,从根源避免数据被覆盖。
- 唯一ID生成:替换
taskList.length +1为Date.now(),解决删除任务后新增任务ID重复的问题。 - 函数式状态更新:使用
prev => [...prev, obj]方式更新状态,确保每次修改基于最新状态值,避免闭包导致的状态不一致。 - 用户体验优化:添加任务后自动清空输入框,提升交互流畅度。
内容的提问来源于stack exchange,提问作者Sahil Sadekar
相关产品推荐
相关产品推荐

