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

ReactJS中localStorage设置异常:首次点击存入空数组

问题解决:useLocalStorage首次存入空数组的问题

问题场景

我是React和TypeScript新手,尝试通过自定义useLocalStorage Hook将值存入localStorage。首次点击“Set Item”按钮时,localStorage会存入空数组,后续操作则正常。已尝试用useEffect初始化空数组,但首次添加数据仍无效,附上相关代码寻求解决方法。

相关代码

hooks/useLocalStorage.ts

function useLocalStorage(key: string) {
  /* Set Item to Localstorage */
  const set = (value: unknown) => {
    try {
      localStorage.setItem(key, JSON.stringify(value));
    } catch (error) {
      console.log((error as Error)?.message);
    }
  };

  /* Get Item from Localstorage */
  const get = () => {
    try {
      return localStorage.getItem(key);
    } catch (error) {
      console.log(error);
    }
  };

  /* Remove Item */
  const clean = () => {
    try {
      localStorage.removeItem(key);
    } catch (error) {
      console.log((error as Error)?.message);
    }
  };

  return { set, get, clean };
}

export { useLocalStorage };

App.tsx

import { Button, ButtonGroup, TextField } from "@mui/material";
import { useState } from "react";
import { useLocalStorage } from "./hooks/useLocalStorage";

function App() {
  type TProp = {
    title: string;
  };

  const [value, setValue] = useState("");
  const { set, get, clean } = useLocalStorage("todos");
  const [todos, setTodos] = useState<TProp[]>([]);

  /* Set Item */
  const setItem = () => {
    setTodos([...todos, { title: value }]);
    set(todos);
  };

  return (
    <>
      <TextField
        label="Enter Text"
        variant="outlined"
        fullWidth
        margin="normal"
        value={value}
        onChange={(e) => setValue(e.target.value)}
      />
      <ButtonGroup variant="contained" fullWidth>
        <Button onClick={setItem}>Set Item</Button>
        <Button color="success" onClick={() => console.log(get())}>
          Get Item
        </Button>
      </ButtonGroup>
      <Button
        variant="contained"
        color="error"
        fullWidth
        sx={{ mt: 2 }}
        onClick={clean}
      >
        Remove Item
      </Button>
    </>
  );
}

export default App;

main.tsx

import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.tsx'

ReactDOM.createRoot(document.getElementById('root')!).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
)

尝试过的无效方案

曾使用useEffect初始化空数组,但首次添加数据仍无效:

/** ... **/

useEffect(() => {
  if(get()?.length === 0) {
    /* todos is empty at this moment */
    set(todos)
  }
},[])

/** ... **/

问题原因

核心问题在于setTodos是异步更新状态的函数:在setItem中,调用setTodos后立刻用当前的todos变量(初始空数组)调用set,此时新的todos状态还未更新完成,所以首次存入的是旧的空数组。后续点击时,todos已经是之前更新后的数组,所以存入正常。

解决方案

方案1:直接使用新创建的数组(快速修复)

修改App.tsx中的setItem函数,先创建新数组,同时更新状态和localStorage,避免依赖异步更新的状态:

const setItem = () => {
  const newTodos = [...todos, { title: value }];
  setTodos(newTodos);
  set(newTodos);
  setValue(""); // 可选:清空输入框提升体验
};

方案2:优化useLocalStorage Hook(推荐)

让Hook内部维护状态,自动同步localStorage,更符合React Hooks的设计逻辑,也避免手动同步的错误:

修改后的hooks/useLocalStorage.ts

import { useState, useEffect } from "react";

function useLocalStorage<T>(key: string, initialValue: T) {
  // 从localStorage读取初始值,没有则用传入的初始值
  const [storedValue, setStoredValue] = useState<T>(() => {
    try {
      const item = localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch (error) {
      console.log((error as Error)?.message);
      return initialValue;
    }
  });

  // 支持函数式更新,同时同步到localStorage
  const setValue = (value: T | ((val: T) => T)) => {
    try {
      const valueToStore = value instanceof Function ? value(storedValue) : value;
      setStoredValue(valueToStore);
      localStorage.setItem(key, JSON.stringify(valueToStore));
    } catch (error) {
      console.log((error as Error)?.message);
    }
  };

  // 监听全局storage变化,处理其他页面修改localStorage的情况(可选)
  useEffect(() => {
    const handleStorageChange = (e: StorageEvent) => {
      if (e.key === key) {
        setStoredValue(e.newValue ? JSON.parse(e.newValue) : initialValue);
      }
    };

    window.addEventListener("storage", handleStorageChange);
    return () => window.removeEventListener("storage", handleStorageChange);
  }, [key, initialValue]);

  // 清除localStorage并重置状态
  const clean = () => {
    try {
      localStorage.removeItem(key);
      setStoredValue(initialValue);
    } catch (error) {
      console.log((error as Error)?.message);
    }
  };

  return { value: storedValue, setValue, clean };
}

export { useLocalStorage };

修改后的App.tsx

import { Button, ButtonGroup, TextField } from "@mui/material";
import { useState } from "react";
import { useLocalStorage } from "./hooks/useLocalStorage";

function App() {
  type TProp = {
    title: string;
  };

  const [value, setValue] = useState("");
  // 使用优化后的Hook,指定初始值为空数组
  const { value: todos, setValue: setTodos, clean } = useLocalStorage<TProp[]>("todos", []);

  const setItem = () => {
    // 使用函数式更新,确保拿到最新的状态
    setTodos(prevTodos => [...prevTodos, { title: value }]);
    setValue(""); // 清空输入框
  };

  return (
    <>
      <TextField
        label="Enter Text"
        variant="outlined"
        fullWidth
        margin="normal"
        value={value}
        onChange={(e) => setValue(e.target.value)}
      />
      <ButtonGroup variant="contained" fullWidth>
        <Button onClick={setItem}>Set Item</Button>
        <Button color="success" onClick={() => console.log(todos)}>
          Get Item
        </Button>
      </ButtonGroup>
      <Button
        variant="contained"
        color="error"
        fullWidth
        sx={{ mt: 2 }}
        onClick={clean}
      >
        Remove Item
      </Button>
    </>
  );
}

export default App;

这种方案下,状态和localStorage会自动同步,无需手动调用set,彻底避免了异步状态更新导致的问题,同时还支持跨页面的localStorage同步。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 13:34:50