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 Root
screen.
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>
);
}