Reputation: 2022
I have a complex navigators hirarchy. But I'll focus on the problem:
Stack navigator
-- Tab navigator
---- Home stack navigator (initial route)
---- Other screens
-- Other screens
The navigation
object that is used to navigate is given by props to a screen or by using the hook useNavigation()
on any screen child component.
I use deep linking in my app to navigate almost everywhere. My main root navigator container is the tab navigator. So it seems very logical to route the user from there to other screens/tabs. I used the navigation
props passed to the tab navigator in useEffect
and it worked fine which surprised me as I'm using it in the navigator (not the screen).
Now I figured what is the issue with this! When the app freshly loads from a quit state, navigation.navigate(...)
just doesn't work in this case (nothing is thrown). It is because the navigation seems to happen before the navigator gets rendered.
I checked this for a possible solution, but it didn't solve my problem as the NavigationContainer
is ready but not the tab navigator.
When I move the navigation to useFocusEffect
on the home screen, it works fine.
How can I verify that the navigator has finished rendering?
Sample code:
function MainTabNav({ navigation }) {
useEffect(() => {
console.log("trying to navigate...");
navigation.navigate("other_screen")
}, [navigation]);
return (
<Tab.Navigator
initialRouteName={...}
>
... // Stack navigators for home & other screens
</Tab.Navigator>
);
}
Upvotes: 1
Views: 2068
Reputation: 96
You can store navigationContainerRef
in a context and expose it through a hook:
const NavigationContainerContext = React.createContext();
const useNavigationContainerRef = () => useContext(NavigationContainerContext);
const MyNavigationContainer = () => {
const navigationContainerRef = useRef(null);
return (
<NavigationContainerContext.Provider value={}>
<NavigationContainer ref={navigationContainerRef}/>
</NavigationContainerContext.Provider>
);
}
And then use another hook manage the navigation logic:
function useMyNavigation(navigation){
const navigate = useCallback(()=>{
const rootState = navigationContainerRef.current?.getRootState();
if(rootState != null){
navigation.navigate(...);
}
});
return {navigate};
}
And then use the navigate
function exposed from the hook on a screen's useEffect
. As your screen is part of the navigation state, the navigation state has to be ready for your screen to render:
const MyScreen = () => {
const navigation = useNavigation();
const myNavigation = useMyNavigation(navigation);
useEffect(()=>myNavigation.navigate(...),[...]);
...
}
Upvotes: 1
Reputation: 2022
After a lot of playing around, I moved all the navigation functionality to my custom tab view renderer (I use ui-kittens
as a UI framework) and it is triggered onLayout
- this verifies that the rendering of the tabs has been done and the navigator can be used from the parent to the child.
function MainTabNav({ navigation }) {
useEffect(() => {
console.log("trying to navigate...");
navigation.navigate("other_screen")
}, [navigation]);
return (
<Tab.Navigator
initialRouteName={...}
tabBar={(props) => <MyTabBar {...props} />}
>
... // Stack navigators for home & other screens
</Tab.Navigator>
);
}
function MyTabBar(){
...
return (
<MyBar onLayout={() => doInitialNavigation()}>
... // tab views
</MyBar>
)
}
function doInitialNavigation(){
... // when you are here the tab navigator has loaded and you can navigate to any tab on app load (even after quit state)
}
Upvotes: 0
Reputation: 333
You could try a conditional before the nagivation.navigate() function to check if navigation is defined.
navigation && navigation.navigate("other_screen")
This will only run if navigation does not equal null or undefined. So your code would look like:
function MainTabNav({ navigation }) {
useEffect(() => {
console.log("trying to navigate...");
navigation && navigation.navigate("other_screen")
}, [navigation]);
return (
<Tab.Navigator
initialRouteName={...}
>
... // Stack navigators for home & other screens
</Tab.Navigator>
);
}
Upvotes: 0