79348767

Date: 2025-01-11 19:48:35
Score: 0.5
Natty:
Report link

Since we cannot place our localStorage calls directly into our initial state (like below) because those will also run on the server side, throwing an undefined error

That's the main problem! Nextjs runs everything that is outside of a useEffect on the server side so you cannot initialize the token with const [token, setToken] = useState(window.localStorage.getItem("token"); directly because localStorage is undefined in the server scope.

You can add a flag in your AuthContext named "initializing" or something like that, and pass it through the AuthContext.Provider value. In that way the consumers of this context value can wait until the AuthContext useEffect run at least once.

Here is an example:

const AuthContext = createContext<AuthContextProps>({
  user: null,
  token: null,
  login: () => {},
  logout: () => {},
  initializing: true,
});

const AuthProvider: React.FC<{ children: React.ReactNode }> = ({
  children,
}) => {
  const [user, setUser] = useState<User | null>(null);
  const [token, setToken] = useState<string | null>(null);
  const [initializing, setInitializing] = useState(true);
  const router = useRouter();

  useEffect(() => {
    console.log("AuthProvider useEffect", localStorage.getItem("token"));
    const storedToken = localStorage.getItem("token");
    if (storedToken) {
      if (isTokenExpired(storedToken)) {
        setToken(null);
        setUser(null);
        localStorage.removeItem("token");
        return;
      }
      setToken(storedToken);
      localStorage.setItem("token", storedToken);
      const decodedToken = jwtDecode<User>(storedToken);
      setUser(decodedToken);
    }
    setInitializing(false);
  }, []);

  const login = (token: string) => {
    setToken(token);
    localStorage.setItem("token", token);
    const decodedToken = jwtDecode<User>(token);
    setUser(decodedToken);
  };

  const logout = () => {
    setToken(null);
    setUser(null);
    localStorage.removeItem("token");
    router.push("/login");
  };

  const isTokenExpired = (token: string) => {
    const decodedToken = jwtDecode<{ exp: number }>(token);
    return Date.now() >= decodedToken.exp * 1000;
  };

  return (
    <AuthContext.Provider value={{ user, token, login, logout, initializing }}>
      {children}
    </AuthContext.Provider>
  );
};

const useAuth = () => {
  const context = React.useContext(AuthContext);
  if (context === undefined) {
    throw new Error("useAuth must be used within an AuthProvider");
  }
  return context;
};

export { AuthContext, AuthProvider, useAuth };

And then you can reuse that initializing flag to know when to control or not yet if the user is loggedIn in the protected pages.

export const useLeads = () => {
  const router = useRouter();
  const { user, token, initializing } = useAuth();
  const [data, setData] = useState<Lead[]>([]);
  const [totalRecords, setTotalRecords] = useState(0);
  const [pageIndex, setPageIndex] = useState(0);
  const [pageSize, setPageSize] = useState(10);
  const [filters, setFilters] = useState({});
  const [loading, setLoading] = useState(false);

  useEffect(() => {
    if (initializing) {
      return;
    }

    if (!token) {
      router.push("/login");
      return;
    }
    const fetchData = async () => {
      setLoading(true);
      try {
        const result = await getLeads(token, pageIndex, pageSize, filters);
        setData(result);

        const count = await countLeads(token, filters);
        setTotalRecords(count);
      } catch (error) {
        console.error("Error fetching data:", error);
      } finally {
        setLoading(false);
      }
    };

    fetchData();
  }, [user, token, pageIndex, pageSize, router, filters, initializing]);

  return {
    data,
    totalRecords,
    pageIndex,
    pageSize,
    filters,
    loading,
    setPageIndex,
    setPageSize,
    setFilters,
  };
};

I encountered this solution in this article https://dev.to/ivandotv/protecting-static-pages-in-next-js-application-1e50

Reasons:
  • Blacklisted phrase (1): this article
  • Long answer (-1):
  • Has code block (-0.5):
  • Low reputation (1):
Posted by: Ale Mendieta