'React Context: infinite loop loading, user never gets logged in
React/NextJs
I decided to add a reducer to my UserDataContext and it just loops endlessly and my user never appears to be logged in.
Is the Provider the right place to trigger all the setup code? or should it be in the hook?
Are there any other issues?
_app.js
import "styles/globals.css";
import Navbar from "components/navbar/Navbar";
import Termsbar from "components/Termsbar";
import Footerbar from "components/Footerbar";
import Drawer from "components/Drawer";
import { AuthProvider, useUserData } from "@/lib/contexts/userdata-context";
import { useRouter } from "next/router";
import { Toaster } from "react-hot-toast";
import HeadPlus from "@/components/HeadPlus";
export default function MyGold({ Component, pageProps }) {
// const {state:{user}} = useUserData(); causes a 500 error
const { state } = useUserData();
const router = useRouter();
const admin = router.route.includes("admin");
return (
<AuthProvider value={state?.user}>
<HeadPlus />
<div className="container rounded-lg mx-1 md:mx-auto h-full debug-screens">
<Navbar />
<Component {...pageProps} />
{admin ? null : <Footerbar />}
<Termsbar />
</div>
<Toaster />
</AuthProvider>
);
}
Then my context, reducer, and provider are all in one file (for now):
import {
createContext,
useState,
useEffect,
useContext,
useReducer,
} from "react";
import nookies from "nookies";
import {
firestore,
auth,
getIdToken,
googleAuthProvider,
AuthContext,
} from "@/lib/firebase";
import { doc, onSnapshot, setDoc, getDoc } from "firebase/firestore";
import { useAuthState } from "react-firebase-hooks/auth";
const initialState = {
user: {},
displayName: null,
userName: null,
photoURL: null,
uid: null,
role: null,
companies: [],
tags: [],
};
const UserDataContext = createContext(initialState);
// U S E A U T H
export const useAuth = () => {
return useContext(AuthContext);
};
// H O O K
// Custom hook to read auth record and user profile doc
export function useUserData() {
const context = useContext(UserDataContext);
return context;
}
/*
* U S E R D A T A R E D U C E R
*/
function userDataReducer(state, action) {
console.log("userDataReducer", state, action.type, action);
let newVals;
switch (action.type) {
case "initial-state":
case "no-logged-in-user":
return initialState;
case "set-auth-state":
return { ...state, ...action.payload };
case "get-user-profile":
console.log("userDataReducer get-user-profile", action);
newVals = getUserProfile(action.payload.user);
return { ...state, ...newVals };
case "set-user-data-firestore":
case "refresh-user":
case "updated-token":
return { ...state, ...action.payload };
case "get-default-tags":
const tags = getDefaultTags();
return { ...state, ...tags };
case "sign-out":
return initialState;
default: {
console.error("userDataReducer error:", action);
throw new Error(`Unhandled action type: ${action.type}`);
}
}
}
/*
* A U T H P R O V I D E R
*/
export function AuthProvider({ value, children }) {
const [state, dispatch] = useReducer(userDataReducer, initialState);
const authUser = auth.currentUser;
console.log("AuthProvider: ", state, auth.currentUser);
useEffect(() => {
if (typeof window !== "undefined") {
window.nookies = nookies;
}
// return is for when the object is destroyed
//let user = state.user;
// auth is from the firestore library functions
return auth.onIdTokenChanged(async (authUser) => {
console.log(`token changed!`);
if (!authUser) {
console.log(`no token found...`);
dispatch({ type: "no-logged-in-user" });
nookies.destroy(null, "token");
nookies.destroy(null, "uid");
nookies.set(null, "token", "", { path: "/" });
nookies.set(null, "uid", "", { path: "/" });
return;
}
console.log(`updating token...`);
const token = await authUser.getIdToken();
console.log("token from user.getIdToken", token);
//setUser(user);
//if (dispatch instanceof Function) {
dispatch({ type: "updated-token", payload: { user: authUser } });
//}
nookies.destroy(null, "token");
nookies.destroy(null, "uid");
nookies.set(null, "token", token, { path: "/" });
nookies.set(null, "uid", authUser.uid, { path: "/" });
});
// end of "return" code
}, []);
// force refresh the token every 30 minutes -- 10
useEffect(() => {
const handle = setInterval(async () => {
console.log(`refreshing token...`);
if (authUser) await authUser.getIdToken(true);
}, 10 * 60 * 1000);
return () => clearInterval(handle);
}, []);
auth.onAuthStateChanged((authUser) => {
console.log("onAuthStateChanged", authUser, state);
console.log("user signed out");
if (authUser.uid !== state?.user.uid) {
dispatch({ type: "get-user-profile", payload: { user: authUser } });
} else {
// technically this should leave tags in the context
if (state.user !== null) {
dispatch({ type: "get-user-profile", payload: initialState });
}
}
});
// S T A R T U P T A S K S
useEffect(() => {
/*
getUserProfile
get info from Firebase to supplement the auth info
*/
// turn off realtime subscription
let unsubscribe;
console.log("dispatch line 158", authUser);
dispatch({ type: "set-auth-state", payload: { user: authUser } });
if (authUser) {
//getUserProfile(user);
if (state.user.uid !== authUser.uid) {
dispatch({ type: "get-user-profile", payload: { user: authUser } });
}
} else {
dispatch({ type: "no-logged-in-user" });
}
dispatch({ type: "get-default-tags" });
return unsubscribe;
}, []);
return (
<UserDataContext.Provider value={{ state, dispatch }}>
{children}
</UserDataContext.Provider>
);
}
// P R O V I D E R E N D S
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|
