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

Рейтинг: 1Ответов: 1Опубликовано: 10.01.2023

я новичок на Nextjs поэтому не судите строго, как исправить ошибку? Hydration failed because the initial UI does not match what was rendered on the server.Я так понимаю что я получаю токен и за счет этого меняется состояние navbar

//часть кода из получения токена
    import jwtDecode from 'jwt-decode'
import React, {useReducer, createContext, FC} from 'react'

const initialState = {
    user: null
}

  if (typeof window !== 'undefined') {
    console.log('You are on the browser')
    // 👉️ can use localStorage here
  
    if(localStorage.getItem("token")) {
        const decodedToken: any = jwtDecode(localStorage.getItem("token") || "")
        if (decodedToken && decodedToken.exp * 1000 < Date.now()) {
            localStorage.removeItem("token")
        } else{
            initialState.user = decodedToken;
        }
    }
    
  } else {
    console.log('You are on the server')
    // 👉️ can't use localStorage
  }


interface UserCtx {
    user: string;
    login: any;
    logout: any;
  }
  
// const AuthContext = React.createContext<UserCtx | "">("");

//old variant
const AuthContext = createContext({
    user: null,
    login: (userData : any) => {},
    logout: () => {} 
});


function authReducer(state: any, action: { type: any; payload: any }) {
    switch(action.type){
        case 'LOGIN':
            return {
                ...state,
                user: action.payload
            }
        case 'LOGOUT':
            return {
                ...state,
                user: null
            }
        default:
            return state;
    }
}

function AuthProvider(props: any) {
    const [state, dispatch] = useReducer(authReducer, initialState)

    const login = (userData: { token: string }) => {
        localStorage.setItem("token", userData.token)
        dispatch({
            type: 'LOGIN',
            payload: userData
        })
    }

    function logout(){
        localStorage.removeItem("token");
        dispatch({
            type: 'LOGOUT',
            payload: undefined
        });
    }

    return (
        <AuthContext.Provider
        value={{user: state.user, login, logout}}
        {...props} /> 
    )
}

export {AuthContext,  AuthProvider};

тот самый NavBar:

import { FC } from "react";
import Link from "next/link";
import Image from "next/image";
import { useRouter } from "next/router";
import styles from "../styles/Navbar.module.scss";
import { AuthContext } from "../hooks/AuthContext";
import React, {useContext} from 'react'

const navigation = [
  { id: 1, title: 'Home', path: '/' },
  { id: 2, title: 'Redux', path: '/redux-toolkit' },
  { id: 3, title: 'login', path: '/auth/login' },
];

const navigationforlogin = [
  { id: 4, title: 'Home', path: '/' },
  { id: 5, title: 'Redux', path: '/redux-toolkit' },
  { id: 6, title: 'logout', path: '/logout' },
];



const Navbar:FC = () => {
  const { pathname } = useRouter();
  const router = useRouter()
  
    const { user, logout } : {user: any, logout: any} = useContext(AuthContext);

    const onLogout = () => {
        logout();
        router.push('/')
    }
    console.log(user)
  return (
    <nav className={styles.nav}>
      <div className={styles.logo}>
        <Image src="/logo.svg" width={60} height={60} alt="webDev" />
      </div>
      <div>
      {user ? 
            <Link href="/" onClick={onLogout}>Logout</Link>
          :
          <div className={styles.links}>
          {navigation.map(({ id, title, path }) => (
            <Link key={id} href={path} legacyBehavior>
              <a className={pathname === path ? styles.active : styles.noactive}>{title}</a>
            </Link>
          ))}
        </div>
        }
        </div>
    </nav>
  );
};

export default Navbar;

введите сюда описание изображения

Ответы

▲ 1Принят

Проблема в коде

if (typeof window !== 'undefined') {

На сервере одно значение и разметка рендерится с учетом initialState.user == null всегда. На клиенте может меняться есть пользователь залогинен.

Для решения нужно перенести определение залогиненности внутрь useEffect - они всегда выполняются на клиенте:

useEffect(()=>{
    if(localStorage.getItem("token")) {
        const decodedToken: any = jwtDecode(localStorage.getItem("token") || "")
        if (decodedToken && decodedToken.exp * 1000 < Date.now()) {
            localStorage.removeItem("token");
        } else{
            dispatch({
                type: 'LOGIN',
                payload: decodedToken
            });
        }
    }
},[])