Renderer more hooks than during the previous render

I’m working on a project with bare expo and react native. I have implemented firebase to my project. The signIn and register are working fine. I have created a auth check to send the user to another page in case he is logged in. Although I am receiving the following error when attempting to send the user to another page:
enter image description here

This is my App.tsx:

import React, { useEffect, useState } from 'react';
import { StatusBar } from 'expo-status-bar';
import { ThemeProvider } from 'styled-components';
import {
    useFonts,
    Poppins_400Regular,
    Poppins_500Medium,
    Poppins_700Bold
} from '@expo-google-fonts/poppins';
import AppLoading from 'expo-app-loading';
import theme from './src/global/styles/theme';
import { NavigationContainer } from '@react-navigation/native';
import { AppRoutes } from './src/routes/app.routes';
import 'intl';
import 'intl/locale-data/jsonp/pt-BR';
import { SignIn } from './src/screens/SignIn';
import auth, { FirebaseAuthTypes } from '@react-native-firebase/auth';

export default function App() {

    const [ fontsLoaded ] = useFonts({
        Poppins_400Regular,
        Poppins_500Medium,
        Poppins_700Bold
    });
    const [user, setUser] = useState<FirebaseAuthTypes.User | null>(null);

    if (!fontsLoaded) {
        return <AppLoading />;
    }

    useEffect(() => {
        const subscriber = auth().onAuthStateChanged(setUser);
        return subscriber;
    }, []);

  return (
        <ThemeProvider theme={theme}>
            <StatusBar style="light"/>
            <NavigationContainer>
                { user ? <AppRoutes /> : <SignIn /> }
            </NavigationContainer>                      
        </ThemeProvider>
  );
}

This is the routes code:

import React from 'react';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { Dashboard } from '../screens/Dashboard';
import { Register } from '../screens/Register';
import { useTheme } from 'styled-components';
import { Platform } from 'react-native';
import { MaterialIcons } from '@expo/vector-icons';
import { Summary } from '../screens/Summary';
 
const { Navigator, Screen } = createBottomTabNavigator();

export function AppRoutes() {
    const theme = useTheme();

    return (
        <Navigator
            screenOptions={{
                headerShown: false,
                tabBarActiveTintColor: theme.colors.orange,
                tabBarInactiveTintColor: theme.colors.texts,
                tabBarLabelPosition: 'beside-icon',
                tabBarStyle: {
                    height: 88,
                    paddingVertical: Platform.OS === 'ios' ? 20 : 0
                }
            }}
        >
            <Screen 
                key="Listing"
                name="Listing"
                component={Dashboard}
                options={{
                    tabBarIcon: (({ size, color }) => 
                        <MaterialIcons 
                            name="format-list-bulleted"
                            size={size}
                            color={color}
                        /> 
                    )
                }}
            />
            <Screen
                key="Register"
                name="Register"
                component={Register}
                options={{
                    tabBarIcon: (({ size, color }) => 
                        <MaterialIcons 
                            name="attach-money"
                            size={size}
                            color={color}
                        /> 
                    )
                }}
            />
            <Screen 
                key="Summary"
                name="Summary"
                component={Summary}
                options={{
                    tabBarIcon: (({ size, color }) => 
                        <MaterialIcons 
                            name="pie-chart"
                            size={size}
                            color={color}
                        /> 
                    )
                }}
            />
        </Navigator>
    );
}

>Solution :

Your issue comes from this block:

if (!fontsLoaded) {
    return <AppLoading />;
}

useEffect(() => {
    const subscriber = auth().onAuthStateChanged(setUser);
    return subscriber;
}, []);

You are conditionally adding a hook here, because if fontsLoaded evaluates to false, you return the render function earlier and the useEffect hook is never run at the first render. However, in subsequent attempts, the fonts are probably loaded, and this causes a different number of hooks being rendered: hence the error you get.

Based on react’s own "rules of hooks" guide, emphasis my own:

Don’t call Hooks inside loops, conditions, or nested functions. Instead, always use Hooks at the top level of your React function, before any early returns. By following this rule, you ensure that Hooks are called in the same order each time a component renders.

To fix this, simply ensure all hooks are defined before any return paths. In your case, this is done by swapping the fontsLoaded check:

// Define all hooks before any return paths!
useEffect(() => {
    const subscriber = auth().onAuthStateChanged(setUser);
    return subscriber;
}, []);


if (!fontsLoaded) {
    return <AppLoading />;
}

return (
    <ThemeProvider theme={theme}>
        <StatusBar style="light"/>
        <NavigationContainer>
            { user ? <AppRoutes /> : <SignIn /> }
        </NavigationContainer>                      
    </ThemeProvider>
);

It is of course sometimes difficult to catch errors like this, especially in a heavy/complicated component. If you’re using eslint, there is an eslint plugin called eslint-plugin-react-hook that will cause eslint to throw a warning/error when this happens.

Leave a Reply