Home > Software engineering >  React Navigation Isn't Re-rendering Nested Drawer Navigator After Async useEffect() Updates Sta
React Navigation Isn't Re-rendering Nested Drawer Navigator After Async useEffect() Updates Sta

Time:01-09

I am implementing an authentication flow where I access AsyncStorage on the top level of navigation to get the user. Root is the component that holds my DrawerNavigator, and I want to pass my user params to that, for auth logic purposes.

// app.js

const App: () => Node = () => {
    [user, setUser] = useState();

    useEffect(() => {
        const getUser = async () => {
            let u = await storageHelpers.getUser()
            if(u !== user) setUser(u);
        }
        getUser()
    }, [user])

    console.log('Rendering', user)
    
    return (
        <NavigationContainer>
            <Stack.Navigator>
                <Stack.Screen initialParams={{user: user}} name="Root" component={Root} options={{ headerShown: false }} />
                <Stack.Screen name="Product" component={ ProductScreen } options={ defaultOptions } />
            </Stack.Navigator>
        </NavigationContainer>
    );
};
// root.js
const Root = ({route, props, navigation}) => {
    const user = route.params ? route.params.user : undefined;
    console.log('User?', user, props, route)
    return (
        <Drawer.Navigator screenOptions={{drawerPosition:"right"}}>
            <Drawer.Screen name="Home" component={HomeScreen} options={defaultOptions}
            <Drawer.Screen name="Scan History" component={ScanHistoryScreen} options={defaultOptions}/>
            <Drawer.Screen name="Languages" component={LanguagesScreen} options={defaultOptions}/>
            <Drawer.Screen name="Report a Problem" component={ReportAProblemScreen} options={defaultOptions}/>
            { user 
                ? <Drawer.Screen name="Logout" component={AuthenticationScreen} />
                : <Drawer.Screen name="Login" component={AuthenticationScreen} />
            }
        </Drawer.Navigator>
    );
}

The way the logs turn out --

 1. app.js renders, user is undefined (expected)
 2. root.js renders, user is undefined (expected)
 3. useEffect hook finishes it's async operation (expected)
 4. app.js renders, user is defined (expected)
 5. *No Rerender On Root.js* (unexpected)

I use the user object in the initialParams of the StackScreen, so I expected it would cause a rerender for root but I guess not.

How can I pass the user object to the rest of my navigation components? I want to keep it as simple as possible, and I would prefer to avoid using a reducer (as ReactNavigation example uses) I'm hoping something similar to my approach would work. Thank you in advance!

CodePudding user response:

React navigation assign initialParams value only when registering and initializing the screen. At this stage, it's undefined and when App.js component fetches user data and rerenders children components including navigation screens.initialParams will not update because this is the next rerender not the screen initialization stage.

As you don't want to use any global state management like Redux or React Context API, you should pass user props directly to Rootscreen.

const App: () => Node = () => {
    [user, setUser] = useState();

    useEffect(() => {
        const getUser = async () => {
            let u = await storageHelpers.getUser()
            if(u !== user) setUser(u);
        }
        getUser()
    }, [user])

    console.log('Rendering', user)
    
    return (
        <NavigationContainer>
            <Stack.Navigator>
                <Stack.Screen initialParams={{user: user}} name="Root" component={(props)= <Root user={user} {...props}/>} options={{ headerShown: false }} />
                <Stack.Screen name="Product" component={ ProductScreen } options={ defaultOptions } />
            </Stack.Navigator>
        </NavigationContainer>
    );
};

// root.js
const Root = ({route, navigation,user}) => {
    console.log('User?', user, route)
    return (
        <Drawer.Navigator screenOptions={{drawerPosition:"right"}}>
            <Drawer.Screen name="Home" component={HomeScreen} options={defaultOptions}
            <Drawer.Screen name="Scan History" component={ScanHistoryScreen} options={defaultOptions}/>
            <Drawer.Screen name="Languages" component={LanguagesScreen} options={defaultOptions}/>
            <Drawer.Screen name="Report a Problem" component={ReportAProblemScreen} options={defaultOptions}/>
            { user 
                ? <Drawer.Screen name="Logout" component={AuthenticationScreen} />
                : <Drawer.Screen name="Login" component={AuthenticationScreen} />
            }
        </Drawer.Navigator>
    );
}

  • Related