vbpkzxczhbdgwrr
vbpkzxczhbdgwrr

Reputation: 141

Client components, localstorage in nextjs13

I am moving the project from react to nextjs and have a problem. I have a global wrapper that handles the login context and is using local storage. I set "use client" directive at the top, but the component is trying to be rendered on the server too, and this code gives a hydration error:

export function AuthProvider(props) {
  
  const initialParsedToken =  typeof localStorage !== 'undefined' ? localStorage.getItem("jwt") : null

  if (initialParsedToken) {
    const decodedToken = jwtDecode<JwtInterface>(initialParsedToken);

    if (decodedToken.exp * 1000 < Date.now()) {
      localStorage.removeItem("token");
    } else {
      initialState.user = decodedToken;
    }
  }

  const [state, dispatch] = useReducer(authReducer, initialState);

  if (typeof localStorage === 'undefined'){
    return null
  }

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

  function logout() {
    localStorage.removeItem("jwt");
    dispatch({ type: "LOGOUT" });
  }

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

The error:

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

How can i use the useReducer hook in next, or make auth logic work with this framework?

I was planning to make render static content as server components, having client parts, but can I avoid using ugly useffect hack in each client component and just add a use client in nested dynamic content?

I tried using useffect hack in parent component, but nested components still don't work.

Upvotes: 3

Views: 3847

Answers (3)

Cl&#233;ment
Cl&#233;ment

Reputation: 11

To complete the accepted answer, the right code would actually be:

if (typeof window !== 'undefined'){
....your local storage logic
}

As typeof returns a string.

Upvotes: 1

Tom
Tom

Reputation: 244

According to the NextJS docs,

Client Components enable you to add client-side interactivity to your application. In Next.js, they are pre-rendered on the server and hydrated on the client.

So yes, they are rendered on the server first. You can use useState to check if the component is mounted:

"use client";
import { useState, useEffect } from "react";

export default function Page() {
  const [mounted, setMounted] = useState(false); // used for detecting renders

  useEffect(() => {
    setMounted(true);
  }, []); // runs once on component mount

  if (!mounted) {
    return <> </>
  }
  return <div>Your page</div>
}

Upvotes: 1

MohamedZh
MohamedZh

Reputation: 466

You should check on the type of window instead to make sure you are on the client side

if (typeof window !== undefined){
....your local storage logic
}

Also, you should consider moving your token to cookies instead of local storage as it doesn't cause conflicts and you can check your cookies on the server side using the context parameter, see this

Upvotes: 5

Related Questions