admin管理员组

文章数量:1291041

I am trying to implement the localstorage hook in NextJS, but getting the following error: Error: Hydration failed because the initial UI does not match what was rendered on the server.. Any ideas of what might cause it? To me, it looks like the useEffect function is not properly used. How do I force the CSR instead of SSR to avoid the hydration error?

import { useState, useEffect } from 'react';

function useLocalStorage<T>(key: string, initialValue: T) {
    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) {
            return initialValue;
        }
    });

    useEffect(() => {
        setStoredValue(storedValue);
    }, [storedValue, setStoredValue]);

    // eslint-disable-next-line no-unused-vars
    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.log(error);
        }
    };

    return [storedValue, setValue] as const;
}

export default useLocalStorage;

I am trying to implement the localstorage hook in NextJS, but getting the following error: Error: Hydration failed because the initial UI does not match what was rendered on the server.. Any ideas of what might cause it? To me, it looks like the useEffect function is not properly used. How do I force the CSR instead of SSR to avoid the hydration error?

import { useState, useEffect } from 'react';

function useLocalStorage<T>(key: string, initialValue: T) {
    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) {
            return initialValue;
        }
    });

    useEffect(() => {
        setStoredValue(storedValue);
    }, [storedValue, setStoredValue]);

    // eslint-disable-next-line no-unused-vars
    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.log(error);
        }
    };

    return [storedValue, setValue] as const;
}

export default useLocalStorage;

Share Improve this question asked Oct 4, 2022 at 7:42 AleksandrsAleksandrs 1111 silver badge8 bronze badges 2
  • Check about this disccussion about the error github./vercel/next.js/discussions/35773 – Rilla Commented Oct 4, 2022 at 13:04
  • You get the hydration error because the initial state you set for the storedValue state variable is different on the server and on the client. Try setting the initial state for that variable inside a useEffect instead. See Warning: Text content did not match. Server: "I'm out" Client: "I'm in" div. – juliomalves Commented Oct 9, 2022 at 20:32
Add a ment  | 

3 Answers 3

Reset to default 4

Try this version of the hook:

import { useEffect, useState } from 'react';

const isServer = typeof window === 'undefined';

export default function useLocalStorage(key, initialValue) {
  // State to store our value
  // Pass initial state function to useState so logic is only executed once
  const [storedValue, setStoredValue] = useState(() => initialValue);

  const initialize = () => {
    if (isServer) {
      return initialValue;
    }
    try {
      // Get from local storage by key
      const item = window.localStorage.getItem(key);
      // Parse stored json or if none return initialValue
      return item ? JSON.parse(item) : initialValue;
    } catch (error) {
      // If error also return initialValue
      console.log(error);
      return initialValue;
    }
  };

  /* prevents hydration error so that state is only initialized after server is defined */
  useEffect(() => {
    if (!isServer) {
      setStoredValue(initialize());
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  // Return a wrapped version of useState's setter function that ...
  // ... persists the new value to localStorage.
  const setValue = (value) => {
    try {
      // Allow value to be a function so we have same API as useState
      const valueToStore =
        value instanceof Function ? value(storedValue) : value;
      // Save state
      setStoredValue(valueToStore);
      // Save to local storage
      if (typeof window !== 'undefined') {
        window.localStorage.setItem(key, JSON.stringify(valueToStore));
      }
    } catch (error) {
      // A more advanced implementation would handle the error case
      console.log(error);
    }
  };
  return [storedValue, setValue];
}

There are two ways which i know to remove Hydration error.

First is to use next/dynamic and load dynamically with ssr option passes as false like this :

   import dynamic from 'next/dynamic'

   const DynamicHeader = dynamic(() => import('../ponents/header'), {
     ssr: false,
   })

But as this is a custom hook and we can only import Component dynamically so you have to use useEffect correctly in this case !

Second is to use useEffect hook. I have done this in Js Hope this helps !

import { useEffect, useState } from "react";

export default function useLocalStorage(key, initialValue) {
const [value, setValue] = useState();

useEffect(() => {
const data = localStorage.getItem(key);
if (data === null) {
  if (typeof initialValue === "function") {
    setValue(initialValue());
  } else {
    setValue(initialValue);
  }
 } else {
  setValue(JSON.parse(data));
 }
 }, []);

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

 return [value, setValue];
 }

Zustand seems to have a solution for this, im currently using Next 13 app router. i was having similar issues so went to zustand docs, there is property called skipHydration which can be used to control when the hydration occurs.

what i did was , made a client ponent called Hydrations, and i have imported all my zustand stores and called the rehydrate function , Zustand docs

Auth slice

    import { create } from 'zustand';
import { persist } from 'zustand/middleware';

interface TypeAuth {
    admin: TypeAdmin | null;
    setAdmin: (admin: TypeAdmin) => void;
}

const useAuth = create<TypeAuth>()(
    persist(
        (set) => ({
            admin: null,
            setAdmin: (admin) => {
                set({ admin });
            },
        }),
        {
            name: 'auth',
            skipHydration: true,
        }
    )
);

export default useAuth;

Hydration File

'use client';

import { useEffect } from 'react';

import { useAuth } from '@/utils/slices';

export default function Hydrations() {
    useEffect(() => {
        useAuth.persist.rehydrate();
    }, []);

    return null;
}

Now just import this Hydration File in your root layout

本文标签: javascriptReact custom localstorage hook hydration error in NextJSStack Overflow