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的变化:
- 组件挂载时
useState的初始化函数仅执行一次,当key切换(比如从profile1-chats变到profile2-chats),不会重新读取新key对应的LocalStorage数据,而是保留旧的state值 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会同步更新为新配置对应的正确数据,避免旧数据被写入新键
修改后切换配置文件时:
userProfile更新,导致useLocalStorage的key参数变化- 监听
key的useEffect触发,读取新key的LocalStorage数据(新配置则用initialState初始化) state更新为新配置的初始值,后续的useEffect只会将新配置的state写入对应的LocalStorage键
若initialState是复杂引用类型(如对象、数组),建议用函数返回新实例(比如() => []),避免因引用复用导致的意外问题。
内容的提问来源于stack exchange,提问作者Emmanuel
相关产品推荐
相关产品推荐

