【问题标题】:NextJS Zustand persist stateNextJS Zusand 持久化状态
【发布时间】:2022-11-07 01:49:00
【问题描述】:

我有这个 github 仓库:https://github.com/salmanfazal01/next-firebase-starter

基本上我正在使用 NextJS、Firebase 和 Zusstand 创建一个基本样板作为全局状态管理器

我无法在 Zusand 中坚持一些状态,如主题、用户等。刷新窗口后,它默认回到默认主题。

我确实尝试了 zustand 提供的持久中间件,即使它有效,它也会导致服务器和客户端的内容不匹配错误

Error: Hydration failed because the initial UI does not match what was rendered on the server.

我将如何在不使用其他库(如 next-themes)的情况下将主题模式保持在状态中?

【问题讨论】:

    标签: reactjs next.js zustand


    【解决方案1】:

    欲望:

    • 在使用服务器端渲染 (SSR) 和 next.js 时,您希望在不同页面之间移动时保持状态。例如,您希望在页面之间移动时显示用户的个人资料图片。

    初步实施:

    • 您将数据保存到用户浏览器的本地存储中。您正在使用 Zusand 来执行此操作。

    问题:

    • 您看到一条错误消息,指出客户端内容与服务器呈现的 html 不匹配。来自 next.js 的示例:
    # Unhandled Runtime Error
    
    Error: Text content does not match server-rendered HTML. See more info here: [https://nextjs.org/docs/messages/react-hydration-error](https://nextjs.org/docs/messages/react-hydration-error)
    
    • 这样做的原因是您正在使用特定变量(例如,user_name)在服务器上呈现 html。然后,当您在客户端加载页面并从客户端本地存储加载 user_name 变量时,与服务器端相比,此变量包含不同的值。这会导致不匹配,错误消息会突出显示 (https://nextjs.org/docs/messages/react-hydration-error)。

    解决方案:

    • 当客户端 html 不同时,在组件/页面首次呈现 html 后从本地存储中加载变量。要使用 next.js 解决此问题,请仅在 useEffect 挂钩中从本地存储加载数据(相当于 Nuxt/Vue 中的 onMounted)。
    • 以 Jotai 为例:
    // store.js
    import { atomWithStorage } from 'jotai/utils'  
      
    export const countAtom = atomWithStorage('countAtom', 0);  
    
    // pages/login.js
    import {useAtom} from "jotai";  
    import {countAtom} from "../stores/store";
    import { useEffect, useState} from "react";
    
    export default function Login(props) {  
    const [count, setCount] = useAtom(countAtom);   // This gets the data from the store.
    
    const add1 = () => {  
      setCount((count) => count + 1);  
    };  
    
    const [displayedCount, setDisplayedCount] = useState();  // Set a local variable.
    useEffect(() => {  
      setDisplayedCount(count);  // Set the local variable from our store data.
    }, [count])
    
    return (  
        <div >  
          <Head>  
            <title>Login</title>  
          </Head>  
    
          <main>  
            <p>  
              { displayedCount }
              Hello from the login  
            <button onClick={add1}>Add 1</button>  
            </p>  
          </main>  
        </div>  
    )  
    }
    
    // pages/index.js
    import {useAtom} from "jotai";  
    import {countAtom} from "../stores/store";
    import { useEffect, useState} from "react";
    
    export default function Home(props) {  
      const [count, setCount] = useAtom(countAtom);   // This gets the data from the store.
    
      const add1 = () => {  
        setCount((count) => count + 1);  
      };  
      
      const [displayedCount, setDisplayedCount] = useState();  // Set a local variable.
      useEffect(() => {  
        setDisplayedCount(count);  // Set the local variable from our store data.
      }, [count])
     
      return (  
          <div >  
            <Head>  
              <title>Home</title>  
            </Head>  
      
            <main>  
              <p>  
                { displayedCount }
                Hello from the home page
              <button onClick={add1}>Add 1</button>  
              </p>  
      
            </main>  
          </div>  
      )  
    }
    

    【讨论】:

      【解决方案2】:

      我想您需要使用 useEffect() 将您的 Zusand 状态转移到 useState() 以防止水合错误。

      您在任何页面/组件中导入的 Zusand State 数据需要在 useEffect() 中更新为新的 useState()。

      https://nextjs.org/docs/messages/react-hydration-error

      【讨论】:

        【解决方案3】:

        此错误是由于 zustand 从持久中间件存储中获取数据并在 NextJS 水合开始完成之前更新其存储引起的。

        如果在第一次触发 useEffect 钩子后渲染任何 zustand 存储变量,则可以简单地避免这种情况。但是,如果您在多个组件中使用多个变量,那么这可能是乏味且糟糕的解决方案。

        为避免这种情况,您可以创建一个 NextJS 布局并等到第一个 useEffect 钩子触发以呈现子元素。

        import { useEffect, useState } from "react"
        
        const CustomLayout = ({ children }) => {
        
            const [isHydrated, setIsHydrated] = useState(false);
        
            //Wait till NextJS rehydration completes
            useEffect(() => {
                setIsHydrated(true);
            }, []);
        
            return (
                <> 
                    {isHydrated ? ( <div>{children}</div> ) : <div>Loading...</div>}
                </>
        
        export default CustomLayout;
        
        
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2011-01-09
          • 1970-01-01
          • 2021-05-20
          • 1970-01-01
          • 1970-01-01
          • 2018-12-28
          • 2021-04-24
          • 2020-12-25
          相关资源
          最近更新 更多