NextJS TypeScript实现useLocalStorage时触发最大更新深度超限错误
解决Next.js TypeScript中LocalStorage上下文的「Maximum update depth exceeded」错误
问题根源
这个错误是状态被反复触发更新导致的循环渲染。结合你的代码来看,核心问题大概率出在useLocalStorage钩子的实现上——要么是服务端渲染阶段直接访问了不存在的localStorage,引发异常后触发状态反复重置;要么是钩子内部逻辑导致每次渲染都触发setState。
修复方案
1. 修正useLocalStorage钩子实现
确保钩子仅在客户端初始化状态,且避免不必要的状态更新:
import { useState, useEffect } from 'react'; export function useLocalStorage<T>(key: string, initialValue: T) { // 服务端返回初始值,客户端才读取localStorage const [storedValue, setStoredValue] = useState<T>(() => { if (typeof window === 'undefined') { return initialValue; } try { const item = window.localStorage.getItem(key); return item ? JSON.parse(item) : initialValue; } catch (error) { console.error(`加载localStorage键 "${key}" 失败:`, error); return initialValue; } }); const setValue = (value: T | ((val: T) => T)) => { try { const valueToStore = value instanceof Function ? value(storedValue) : value; setStoredValue(valueToStore); if (typeof window !== 'undefined') { window.localStorage.setItem(key, JSON.stringify(valueToStore)); } } catch (error) { console.error(`设置localStorage键 "${key}" 失败:`, error); } }; // 同步其他标签页的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]); return [storedValue, setValue] as const; }
2. 优化上下文提供者
因为invoices是引用类型对象,若每次渲染都生成新引用,会导致上下文提供者反复触发子组件重渲染。用useMemo缓存invoices:
import { InvoiceContext, SetInvoicesContext, useInvoices } from '@/Context/InvoiceContext'; import { PropsWithChildren, useMemo } from 'react'; export const InvoicesContextProvider = ({ children }: PropsWithChildren) => { const [invoices, setInvoices] = useInvoices(); // 缓存invoices,避免不必要的上下文更新 const cachedInvoices = useMemo(() => invoices, [invoices]); return ( <InvoiceContext.Provider value={cachedInvoices}> <SetInvoicesContext.Provider value={setInvoices}> {children} </SetInvoicesContext.Provider> </InvoiceContext.Provider> ); };
3. 检查上下文消费组件
确保useSetInvoiceContext返回的setInvoices只在事件处理函数或副作用中调用,绝对不能在组件渲染阶段直接调用:
- 错误示例(会触发无限循环):
// ❌ 禁止:渲染阶段调用setInvoices const InvoiceComponent = () => { const setInvoices = useSetInvoiceContext(); setInvoices(prev => ({...prev})); return <div>...</div>; };
- 正确示例:
// ✅ 正确:仅在事件中调用 const InvoiceComponent = () => { const setInvoices = useSetInvoiceContext(); const handleAddInvoice = () => { setInvoices(prev => ({ invoices: [...prev.invoices, { title: '新发票', items: [], note: '', status: '' }] })); }; return <button onClick={handleAddInvoice}>添加发票</button>; };
验证步骤
- 替换原有
useLocalStorage钩子为修正后的版本 - 更新上下文提供者,添加
useMemo缓存逻辑 - 排查所有消费上下文的组件,确保
setInvoices的调用时机正确
内容的提问来源于stack exchange,提问作者Book Teeranai
相关产品推荐
相关产品推荐

