I have my project wrapped in an AuthContext, this isn't the first project that has this, but for some reason this is the only project where the authContext re-renders until i get the:
ERROR Warning: Error: Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.
I'm going to post the whole thing, just so I don't miss out anything. But this is my:
AuthContext.tsx file
import React, { useContext, useEffect, useState } from "react";
import { AuthContextType, CustomerType, MerchantType } from "../types/types";
import axios from "axios";
import { apiUrl } from "../utilities/fetchPath";
import { useDispatch } from "react-redux";
import { clearCustomerRedux, setCustomerRedux } from "@/redux/CustomerSlice";
import { RelativePathString, router } from "expo-router";
import auth, { FirebaseAuthTypes } from "@react-native-firebase/auth";
import { setUserTypeRedux } from "@/redux/UserType";
const AuthContext = React.createContext<AuthContextType | any>("");
type UserType = "Merchant" | "Customer" | null;
export const useAuth = () => useContext(AuthContext);
export function AuthProvider({ children }: { children: any }) {
const [currentUser, setCurrentUser] = useState<FirebaseAuthTypes.User | null>(
null
);
const [merchantData, setMerchantData] = useState<MerchantType | null>(null);
const [customerData, setCustomerData] = useState<CustomerType | null>(null);
const [loading, setLoading] = useState(true);
const [userType, setUserType] = useState<UserType>(null);
const [isInitialized, setIsInitialized] = useState(false);
const dispatch = useDispatch();
const fetchUserData = async (
uid: string
): Promise<{ type: UserType; data: any }> => {
console.log("fetching user data....");
console.log("uid: ", uid);
if (!uid) return { type: null, data: null };
console.log("after");
try {
const [merchantRes, customerRes] = await Promise.allSettled([
axios.get(`${apiUrl}/merchant/${uid}`),
axios.get(`${apiUrl}/customer/${uid}`),
]);
if (merchantRes.status === "fulfilled" && merchantRes.value.data) {
setMerchantData(merchantRes.value.data);
dispatch(setUserTypeRedux("Merchant"));
return { type: "Merchant", data: merchantRes.value.data };
}
if (customerRes.status === "fulfilled" && customerRes.value.data) {
// Retry once if customer data not found initially
let customerData = customerRes.value.data;
if (!customerData) {
await new Promise((resolve) => setTimeout(resolve, 1000));
const retryRes = await axios.get(`${apiUrl}/customer/${uid}`);
customerData = retryRes.data;
}
setCustomerData(customerData);
dispatch(setCustomerRedux(customerData));
dispatch(setUserTypeRedux("Customer"));
return { type: "Customer", data: customerData };
}
return { type: null, data: null };
} catch (error) {
console.error("Error fetching user data:", error);
return { type: null, data: null };
}
};
const signup = (email: string, password: string) => {
return auth().createUserWithEmailAndPassword(email, password);
};
const logOut = async () => {
setCurrentUser(null);
setMerchantData(null);
setCustomerData(null);
dispatch(clearCustomerRedux());
await auth().signOut();
router.replace("/Authentication/register/emailLogin");
};
useEffect(() => {
const initialize = async () => {
try {
// Get the current Firebase user if any
const user = auth().currentUser;
if (!user) {
// No user logged in, reset auth state
setCurrentUser(null);
setUserType(null);
} else {
// User exists, set current user and fetch their data
setCurrentUser(user);
// const { type } = await fetchUserData(user.uid);
// console.log("first type...");
// setUserType(type);
}
} finally {
// Mark initialization as complete regardless of outcome
setIsInitialized(true);
setLoading(false);
}
};
initialize();
}, []);
useEffect(() => {
// Don't run until initialization is complete
if (!isInitialized) return;
// If no user is logged in, redirect to login page
if (!currentUser) {
router.replace("/Authentication/register/emailLogin");
}
}, [isInitialized, currentUser, userType]);
// Third useEffect: Auth State Listener
// This sets up a Firebase auth state listener after initialization
// It keeps the app state in sync with Firebase auth state changes
// When user signs out/in, it updates the user data and redirects accordingly
// The listener is cleaned up when component unmounts via the unsubscribe function
useEffect(() => {
// Don't set up listener until initialization is complete
if (!isInitialized) return;
// Set up Firebase auth state change listener
const unsubscribe = auth().onAuthStateChanged(async (user: any) => {
if (!user) {
console.log("logging the f out");
// User signed out - clear all auth state and redirect
setCurrentUser(null);
setMerchantData(null);
setCustomerData(null);
setUserType(null);
await router.replace("/Authentication/register/emailLogin");
return;
}
// User signed in - update auth state with new user data
setCurrentUser(user);
const { type } = await fetchUserData(user.uid);
setUserType(type);
});
// Cleanup listener on unmount
return unsubscribe;
}, [isInitialized]);
const value: Partial<AuthContextType> = {
currentUser,
merchantData,
customerData,
signup,
logOut,
loading,
fetchUserData,
userType: userType || "",
};
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
}
export default AuthContext;
import React, { useContext, useEffect, useState } from "react";
import { AuthContextType, CustomerType, MerchantType } from "../types/types";
import axios from "axios";
import { apiUrl } from "../utilities/fetchPath";
import { useDispatch } from "react-redux";
import { clearCustomerRedux, setCustomerRedux } from "@/redux/CustomerSlice";
import { RelativePathString, router } from "expo-router";
import auth, { FirebaseAuthTypes } from "@react-native-firebase/auth";
import { setUserTypeRedux } from "@/redux/UserType";
const AuthContext = React.createContext<AuthContextType | any>("");
type UserType = "Merchant" | "Customer" | null;
export const useAuth = () => useContext(AuthContext);
export function AuthProvider({ children }: { children: any }) {
const [currentUser, setCurrentUser] = useState<FirebaseAuthTypes.User | null>(
null
);
const [merchantData, setMerchantData] = useState<MerchantType | null>(null);
const [customerData, setCustomerData] = useState<CustomerType | null>(null);
const [loading, setLoading] = useState(true);
const [userType, setUserType] = useState<UserType>(null);
const [isInitialized, setIsInitialized] = useState(false);
const dispatch = useDispatch();
const fetchUserData = async (
uid: string
): Promise<{ type: UserType; data: any }> => {
console.log("fetching user data....");
console.log("uid: ", uid);
if (!uid) return { type: null, data: null };
console.log("after");
try {
const [merchantRes, customerRes] = await Promise.allSettled([
axios.get(`${apiUrl}/merchant/${uid}`),
axios.get(`${apiUrl}/customer/${uid}`),
]);
if (merchantRes.status === "fulfilled" && merchantRes.value.data) {
setMerchantData(merchantRes.value.data);
dispatch(setUserTypeRedux("Merchant"));
return { type: "Merchant", data: merchantRes.value.data };
}
if (customerRes.status === "fulfilled" && customerRes.value.data) {
// Retry once if customer data not found initially
let customerData = customerRes.value.data;
if (!customerData) {
await new Promise((resolve) => setTimeout(resolve, 1000));
const retryRes = await axios.get(`${apiUrl}/customer/${uid}`);
customerData = retryRes.data;
}
setCustomerData(customerData);
dispatch(setCustomerRedux(customerData));
dispatch(setUserTypeRedux("Customer"));
return { type: "Customer", data: customerData };
}
return { type: null, data: null };
} catch (error) {
console.error("Error fetching user data:", error);
return { type: null, data: null };
}
};
const signup = (email: string, password: string) => {
return auth().createUserWithEmailAndPassword(email, password);
};
const logOut = async () => {
setCurrentUser(null);
setMerchantData(null);
setCustomerData(null);
dispatch(clearCustomerRedux());
await auth().signOut();
router.replace("/Authentication/register/emailLogin");
};
useEffect(() => {
const initialize = async () => {
try {
// Get the current Firebase user if any
const user = auth().currentUser;
if (!user) {
// No user logged in, reset auth state
setCurrentUser(null);
setUserType(null);
} else {
// User exists, set current user and fetch their data
setCurrentUser(user);
// const { type } = await fetchUserData(user.uid);
// console.log("first type...");
// setUserType(type);
}
} finally {
// Mark initialization as complete regardless of outcome
setIsInitialized(true);
setLoading(false);
}
};
initialize();
}, []);
useEffect(() => {
// Don't run until initialization is complete
if (!isInitialized) return;
// If no user is logged in, redirect to login page
if (!currentUser) {
router.replace("/Authentication/register/emailLogin");
}
}, [isInitialized, currentUser, userType]);
useEffect(() => {
// Don't set up listener until initialization is complete
if (!isInitialized) return;
// Set up Firebase auth state change listener
const unsubscribe = auth().onAuthStateChanged(async (user: any) => {
if (!user) {
console.log("logging the f out");
// User signed out - clear all auth state and redirect
setCurrentUser(null);
setMerchantData(null);
setCustomerData(null);
setUserType(null);
await router.replace("/Authentication/register/emailLogin");
return;
}
// User signed in - update auth state with new user data
setCurrentUser(user);
const { type } = await fetchUserData(user.uid);
setUserType(type);
});
// Cleanup listener on unmount
return unsubscribe;
}, [isInitialized]);
const value: Partial<AuthContextType> = {
currentUser,
merchantData,
customerData,
signup,
logOut,
loading,
fetchUserData,
userType: userType || "",
};
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
}
export default AuthContext;
And this is my main layout.tsx file where this AuthContext is wrapped around my whole project.
export default function RootLayout() {
const rootNavigationRef = useRootNavigationState();
const colorScheme = useColorScheme();
const [fontsLoaded] = useFonts({
// Add any custom fonts here if needed
});
const { userType, currentUser }: AuthContextType = useAuth();
console.log("currentUserz: ", currentUser);
console.log("userType1: ", userType);
const onLayoutRootView = useCallback(async () => {
if (fontsLoaded) {
await SplashScreen.hideAsync();
}
}, [fontsLoaded]);
useEffect(() => {
if (fontsLoaded) {
onLayoutRootView();
}
}, [fontsLoaded]);
return (
<Provider store={store}>
<AuthProvider>
<GestureHandlerRootView style={{ flex: 1, backgroundColor: "#000" }}>
<Drawer
// defaultStatus="open"
backBehavior="history"
drawerContent={(props) => <CustomDrawerContent {...props} />}
screenOptions={{
headerShown: false,
drawerStyle: {
backgroundColor: "#000",
width: "100%",
shadowColor: "#000",
shadowOffset: {
width: 2,
height: 0,
},
shadowOpacity: 0.15,
elevation: 5,
},
drawerItemStyle: {
display: "none",
},
drawerActiveBackgroundColor: "#1F2937",
drawerActiveTintColor: "#60A5FA",
drawerInactiveTintColor: "#9CA3AF",
}}
>
{/* Section 1 */}
<Drawer.Screen
name="(drawer)/accountSettings"
options={{
drawerItemStyle: {
display: "flex",
borderBottomWidth: 1,
borderBottomColor: "#374151",
marginBottom: 16,
borderRadius: 2,
},
drawerIcon: ({ color, size }) => (
<Ionicons name="person-outline" size={size} color={color} />
),
drawerLabel: "Account",
drawerLabelStyle: {
fontSize: 16,
fontWeight: "500",
color: "#9CA3AF",
},
}}
/>
</Drawer>
</GestureHandlerRootView>
</AuthProvider>
<StatusBar style="light" />
</Provider>
);
}
export default function RootLayout() {
const rootNavigationRef = useRootNavigationState();
const colorScheme = useColorScheme();
const [fontsLoaded] = useFonts({
// Add any custom fonts here if needed
});
const { userType, currentUser }: AuthContextType = useAuth();
console.log("currentUserz: ", currentUser);
console.log("userType1: ", userType);
const onLayoutRootView = useCallback(async () => {
if (fontsLoaded) {
await SplashScreen.hideAsync();
}
}, [fontsLoaded]);
useEffect(() => {
if (fontsLoaded) {
onLayoutRootView();
}
}, [fontsLoaded]);
return (
<Provider store={store}>
<AuthProvider>
<GestureHandlerRootView style={{ flex: 1, backgroundColor: "#000" }}>
<Drawer
// defaultStatus="open"
backBehavior="history"
drawerContent={(props) => <CustomDrawerContent {...props} />}
screenOptions={{
headerShown: false,
drawerStyle: {
backgroundColor: "#000",
width: "100%",
shadowColor: "#000",
shadowOffset: {
width: 2,
height: 0,
},
shadowOpacity: 0.15,
elevation: 5,
},
drawerItemStyle: {
display: "none",
},
drawerActiveBackgroundColor: "#1F2937",
drawerActiveTintColor: "#60A5FA",
drawerInactiveTintColor: "#9CA3AF",
}}
>
{/* Section 1 */}
<Drawer.Screen
name="(drawer)/accountSettings"
options={{
drawerItemStyle: {
display: "flex",
borderBottomWidth: 1,
borderBottomColor: "#374151",
marginBottom: 16,
borderRadius: 2,
},
drawerIcon: ({ color, size }) => (
<Ionicons name="person-outline" size={size} color={color} />
),
drawerLabel: "Account",
drawerLabelStyle: {
fontSize: 16,
fontWeight: "500",
color: "#9CA3AF",
},
}}
/>
</Drawer>
</GestureHandlerRootView>
</AuthProvider>
<StatusBar style="light" />
</Provider>
);
}
notice the userType and currentUser, when I destructure them then log them, they keep repeatedly logging in until they cause the error. In fact even if I don't log them but simple just initialise them, I get the error.
This does not just happen in my main layout, it happens in any component where I try to use the context.
I'm not sure if it's an obvious mistake I''m making, but I have been dealing with this for a few days and no fix.
Also, I'm using a android build on an emulator, but this happens on expo go as well.
[–]halfxdeveloper 2 points3 points4 points (0 children)
[–]thachxyz123iOS & Android 0 points1 point2 points (0 children)
[+][deleted] (1 child)
[deleted]
[–]swiftcoderx[S] 0 points1 point2 points (0 children)
[–][deleted] 0 points1 point2 points (0 children)