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

Next.js切换用户配置文件时LocalStorage数据异常排查

问题描述

我正在开发一个仿ChatGPT的React/Next.js应用,用自定义useLocalStorage钩子管理不同用户配置文件的LocalStorage数据。每个配置对应独立的LocalStorage键(如profile1-chats、profile2-chats),通过当前用户配置动态生成钩子的键。

切换配置文件时,应用状态已正确更新,但新配置的LocalStorage会复制旧配置的数据,导致异常行为。

相关代码

ChatProvider代码

export const ChatProvider: React.FC<ChatProviderProps> = ({ children }) => {
  const [userProfile, setUserProfile] = useState<ProfileType>(
    UserProfiles.Profile1
  );

  const [chats, setChats] = useLocalStorage(
    `${userProfile.title}-chats`,
    initialState
  );

// ....

自定义useLocalStorage钩子

// CUSTOM HOOK TO HANDLE LOCAL STORAGE IN NEXTJS
import { useEffect, useState } from "react";

const useLocalStorage = <T>(
  key: string,
  initialState: T
): [T, React.Dispatch<React.SetStateAction<T>>] => {
  const [state, setState] = useState<T>(() => {
    if (typeof window !== "undefined") {
      try {
        const item = localStorage.getItem(key);
        return item ? JSON.parse(item) : initialState;
      } catch (error) {
        console.error("Error parsing local storage data:", error);
      }
    }
    return initialState;
  });

  useEffect(() => {
    if (typeof window !== "undefined") {
      try {
        localStorage.setItem(key, JSON.stringify(state)); // 怀疑此处切换配置时,state仍保留旧数据并写入新键
      } catch (error) {
        console.error("Error saving data to local storage:", error);
      }
    }
  }, [key, state]);

  return [state, setState];
};

export default useLocalStorage;

切换配置文件的组件代码

const SelectProfileModal = ({
  isOpen,
  onClose,
  onConfirm,
}: SelectProfileModalProps) => {
  const { userProfile, setUserProfile } = useChatContext();
  const router = useRouter();
  const [selectedProfile, setSelectedProfile] = 
    useState<ProfileType>(userProfile);

  const handleOnSave = () => {
    setUserProfile(selectedProfile);
    router.push("/");
    onClose();
  };

  return (
    <Modal show={isOpen} onHide={onClose}>
      <ModalHeader closeButton>
        <Modal.Title>Select Profile</Modal.Title>
      </ModalHeader>
      <ModalBody>
        <ListGroup>
          {AvailableProfiles.map((profile) => (
            <ListGroup.Item
              key={profile.id}
              action
              variant="dark"
              onClick={() => setSelectedProfile(profile)}
              active={selectedProfile.id === profile.id}
            >
              {profile.id}- {profile.title}
            </ListGroup.Item>
          ))}
        </ListGroup>
      </ModalBody>
      <ModalFooter>
        <Button variant="secondary" onClick={onClose}>
          Cancel
        </Button>
        <Button variant="primary" onClick={handleOnSave}>
          Select
        </Button>
      </ModalFooter>
    </Modal>
  );
};

问题现象

使用Profile1提问后,数据正确存入profile1-chats;切换到Profile2时,profile2-chats被初始化但复制了profile1-chats的数据,而非空数组。

期望结果

切换到新配置文件时,对应的LocalStorage键初始化为空数组,新数据仅存入对应键中。

技术栈:next@13.4.12,react@18.2.0


解决方案

问题根源在于useLocalStorage钩子没有正确响应key的变化:

  1. 组件挂载时useState的初始化函数仅执行一次,当key切换(比如从profile1-chats变到profile2-chats),不会重新读取新key对应的LocalStorage数据,而是保留旧的state值
  2. key更新后,useEffect触发,直接将旧的state写入新key,导致新配置被填充旧数据

修改useLocalStorage钩子,使其能监听key变化并重新初始化状态:

import { useEffect, useState } from "react";

const useLocalStorage = <T>(
  key: string,
  initialState: T
): [T, React.Dispatch<React.SetStateAction<T>>] => {
  const [state, setState] = useState<T>(() => {
    if (typeof window !== "undefined") {
      try {
        const item = localStorage.getItem(key);
        return item ? JSON.parse(item) : initialState;
      } catch (error) {
        console.error("Error parsing local storage data:", error);
      }
    }
    return initialState;
  });

  // 监听key变化,重新从localStorage读取数据或初始化
  useEffect(() => {
    if (typeof window !== "undefined") {
      try {
        const item = localStorage.getItem(key);
        setState(item ? JSON.parse(item) : initialState);
      } catch (error) {
        console.error("Error reinitializing local storage data:", error);
        setState(initialState);
      }
    } else {
      setState(initialState);
    }
  }, [key, initialState]);

  // 保存state到localStorage
  useEffect(() => {
    if (typeof window !== "undefined") {
      try {
        localStorage.setItem(key, JSON.stringify(state));
      } catch (error) {
        console.error("Error saving data to local storage:", error);
      }
    }
  }, [key, state]);

  return [state, setState];
};

export default useLocalStorage;

关键修改点

  • 添加了一个监听key变化的useEffect,当key更新时,重新读取新key的LocalStorage值,不存在则用initialState重置
  • 确保key变化时,state会同步更新为新配置对应的正确数据,避免旧数据被写入新键

修改后切换配置文件时:

  1. userProfile更新,导致useLocalStorage的key参数变化
  2. 监听key的useEffect触发,读取新key的LocalStorage数据(新配置则用initialState初始化)
  3. state更新为新配置的初始值,后续的useEffect只会将新配置的state写入对应的LocalStorage键

若initialState是复杂引用类型(如对象、数组),建议用函数返回新实例(比如() => []),避免因引用复用导致的意外问题。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 17:43:19