如何在使用Next.js时修复与localStorage一起使用的Hydration错误?

huangapple go评论72阅读模式
英文:

How to fix the Hydration error when using localStorage with Next.js?

问题

我尝试将上下文保存在localStorage中在Next.js项目中,但刷新页面时出现了水合错误。

如何解决这个问题?

type AppState = {
  name: string;
  salary: number;
  info: {
    email: string;
    department: string;
  };
};

interface Props {
  children: ReactNode;
}

const initialState: AppState = {
  name: "osamu1908",
  salary: 2000,
  info: {
    email: "dynamic@gmail.com",
    department: "software developer",
  },
};

export const AppState = createContext<AppState>(initialState);
export const AppStateSet = createContext<Dispatch<SetStateAction<AppState>>>(
  () => {}
);

const initalFromLocalStorage = () => {
  if (typeof window !== "undefined") {
    const result = localStorage.getItem("state");

    if (result) {
      return JSON.parse(result) as AppState;
    }
  }

  return initialState;
};

const AppStateProvider = ({ children }: Props) => {
  const [state, setState] = useState<AppState>(initalFromLocalStorage());

  useEffect(() => {
    localStorage.setItem("state", JSON.stringify(state));
  }, [state]);

  return (
    <AppState.Provider value={state}>
      <AppStateSet.Provider value={setState}>{children}</AppStateSet.Provider>
    </AppState.Provider>
  );
};

export default AppStateProvider;
英文:

I'm trying to persist context in localStorage in a Next.js project, but I get a hydration error when I refresh the page.

How can I solve this problem?

type AppState = {
  name: string;
  salary: number;
  info: {
    email: string;
    department: string;
  };
};

interface Props {
  children: ReactNode;
}

const initialState: AppState = {
  name: &quot;osamu1908&quot;,
  salary: 2000,
  info: {
    email: &quot;dynamic@gmail.com&quot;,
    department: &quot;software developer&quot;,
  },
};

export const AppState = createContext&lt;AppState&gt;(initialState);
export const AppStateSet = createContext&lt;Dispatch&lt;SetStateAction&lt;AppState&gt;&gt;&gt;(
  () =&gt; {}
);

const initalFromLocalStorage = () =&gt; {
  if (typeof window !== &quot;undefined&quot;) {
    const result = localStorage.getItem(&quot;state&quot;);

    if (result) {
      return JSON.parse(result) as AppState;
    }
  }

  return initialState;
};

const AppStateProvider = ({ children }: Props) =&gt; {
  const [state, setState] = useState&lt;AppState&gt;(initalFromLocalStorage());

  useEffect(() =&gt; {
    localStorage.setItem(&quot;state&quot;, JSON.stringify(state));
  }, [state]);

  return (
    &lt;AppState.Provider value={state}&gt;
      &lt;AppStateSet.Provider value={setState}&gt;{children}&lt;/AppStateSet.Provider&gt;
    &lt;/AppState.Provider&gt;
  );
};

export default AppStateProvider;

答案1

得分: 1

以下是您要翻译的内容:

"初始渲染应该在客户端和服务器上保持一致。任何时候违反此规则,都会出现hydration错误,就像在您的情况下一样,服务器上没有localStorage,而在浏览器上存在,导致不同的初始状态。

您应该将实例化主题的逻辑移动到useEffect内部:

const AppStateProvider = ({ children }: Props) =&gt; {
  const [state, setState] = useState&lt;AppState | null&gt;(null);

  useEffect(() =&gt; {
    if (state) {
      localStorage.setItem(&quot;state&quot;, JSON.stringify(state));
    }
  }, [state]);

  useEffect(() =&gt; {
    setState(initalFromLocalStorage());
  }, []);

  // 这样做是为了在上下文完全设置之前不显示任何内容。您还可以设置一个加载器或其他内容。
  if (!state) {
    return null;
  }

  return (
    &lt;AppState.Provider value={state}&gt;
      &lt;AppStateSet.Provider value={setState}&gt;{children}&lt;/AppStateSet.Provider&gt;
    &lt;/AppState.Provider&gt;
  );
};

而且在这一点上,您可以将initalFromLocalStorage 简化如下:

const initalFromLocalStorage = () =&gt; {
  const result = localStorage.getItem(&quot;state&quot;);

  if (result) {
    return JSON.parse(result) as AppState;
  }

  return initialState;
};
英文:

The initial render should be the same on the client and server. Anytime you violate this rule, you get a hydration error, like in your case, where there will be no localStorage on the server, and it's present on the browser, leading to a different initial state.

You should move the logic of instantiating the theme inside an useEffect:

const AppStateProvider = ({ children }: Props) =&gt; {
  const [state, setState] = useState&lt;AppState | null&gt;(null);

  useEffect(() =&gt; {
    if (state) {
      localStorage.setItem(&quot;state&quot;, JSON.stringify(state));
    }
  }, [state]);

  useEffect(() =&gt; {
    setState(initalFromLocalStorage());
  }, []);

  // This is so you don&#39;t show anything before the context is fully set. You could also set a loader or something.
  if (!state) {
    return null;
  }

  return (
    &lt;AppState.Provider value={state}&gt;
      &lt;AppStateSet.Provider value={setState}&gt;{children}&lt;/AppStateSet.Provider&gt;
    &lt;/AppState.Provider&gt;
  );
};

And at this point, you can simplify initalFromLocalStorage as below:

const initalFromLocalStorage = () =&gt; {
  const result = localStorage.getItem(&quot;state&quot;);

  if (result) {
    return JSON.parse(result) as AppState;
  }

  return initialState;
};

huangapple
  • 本文由 发表于 2023年5月7日 17:14:39
  • 转载请务必保留本文链接:https://go.coder-hub.com/76193045.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定