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

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>;
};

验证步骤

  1. 替换原有useLocalStorage钩子为修正后的版本
  2. 更新上下文提供者,添加useMemo缓存逻辑
  3. 排查所有消费上下文的组件,确保setInvoices的调用时机正确

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 02:50:28