I'm facing a problem with nesting a Drawer Navigator inside a BottomTabs Navigator using React Navigation in my React Native app. I've provided the relevant code below, and despite multiple attempts, I'm encountering an error related to the TOGGLE_DRAWER action not being handled by any navigator.
Custom drawer component MyDrawerContent that I pass to drawerContent prop of Drawer.Navigator
In my ExpensesOverview function I have BottomTabs.Navigator where I call toggleDrawer
function ExpensesOverview() {
const expensesCtx = useContext(ExpensesContext);
return (
<BottomTabs.Navigator
screenOptions={({navigation}) => ({
headerStyle: {backgroundColor: GlobalStyles.colors.primary500},
headerTintColor: 'white',
tabBarStyle: {backgroundColor: GlobalStyles.colors.primary500},
tabBarActiveTintColor: GlobalStyles.colors.accent500,
// headerLeft: () => <CustomHeader />, // Use CustomHeader here
// headerLeft: () => renderDrawerIcon(navigation),
headerLeft: () => (
<IconButton
icon="menu"
size={30}
color="white"
onPress={() => {
navigation.dispatch(DrawerActions.toggleDrawer());
}}
/>
),
)}
Then, in my App function I have the NavigationContainer > Stack.Navigator and the ExpensesOverview, ManageExpense(imported component) and lastly the DrawerNavigator. Placing DrawerNavigator last is the only way I get bottom tabs to appear and the Drawer navigator in header left.
If I place Drawer navigator as the first Stack.Screen I can toggle the drawer but the bottom tabs don't appear.
If I place the Stack.Screen Drawer navigator last, the bottom tabs do appear as does the Drawer navigator but when I press on the Drawer I get error:
`ERROR The action 'TOGGLE_DRAWER' was not handled by any navigator.
Is your screen inside a Drawer navigator?`
//Found inside App function
return (
<>
<StatusBar style="light" />
<NavigationContainer>
<Stack.Navigator
screenOptions={{
headerStyle: {backgroundColor: GlobalStyles.colors.primary500},
headerTintColor: 'white',
}}>
{isAuthenticated ? (
<>
<Stack.Screen
name="ExpensesOverview"
component={ExpensesOverview}
options={{headerShown: false}}
/>
<Stack.Screen
name="ManageExpense"
component={ManageExpense}
options={{
presentation: 'modal',
headerShown: true,
}}
/>
<Stack.Screen
name="DrawerNavigator"
component={MyDrawer}
options={{headerShown: true}}
/>
</>
) : (
<Stack.Screen
name="LoginScreen"
component={LoginScreen}
options={{headerShown: false}}
/>
)}
</Stack.Navigator>
</NavigationContainer>
</>
);
Below is the whole App.jsx file for context:
const Stack = createNativeStackNavigator()
const BottomTabs = createBottomTabNavigator()
const Drawer = createDrawerNavigator();
function MyDrawerContent({ navigation }) {
return (
<DrawerContentScrollView>
<DrawerItem
label="Recent Expenses"
onPress={() => {
navigation.dispatch(DrawerActions.closeDrawer())
navigation.navigate('Recent')
}}
/>
</DrawerContentScrollView>
)
}
function MyDrawer() {
return (
<Drawer.Navigator
drawerContent={(props) => <MyDrawerContent {...props} />}>
<Drawer.Screen name="Recent" component={RecentExpenses} />
</Drawer.Navigator>
);
}
const renderDrawerIcon = (navigation) => (<IconButton
icon="menu"
size={30}
color="white"
onPress={() =>
navigation.dispatch(
DrawerActions.toggleDrawer(),
)
} // Open the Drawer when the button is pressed
/>
)
function ExpensesOverview() {
const expensesCtx = useContext(ExpensesContext);
return (
<BottomTabs.Navigator
screenOptions={({navigation}) => ({
headerStyle: {backgroundColor: GlobalStyles.colors.primary500},
headerTintColor: 'white',
tabBarStyle: {backgroundColor: GlobalStyles.colors.primary500},
tabBarActiveTintColor: GlobalStyles.colors.accent500,
// headerLeft: () => <CustomHeader />, // Use CustomHeader here
// headerLeft: () => renderDrawerIcon(navigation),
headerLeft: () => (
<IconButton
icon="menu"
size={30}
color="white"
// onPress={() => navigation.dispatch(DrawerActions.toggleDrawer(MyDrawer))}
onPress={() => {
navigation.dispatch(DrawerActions.toggleDrawer());
}}
/>
),
headerRight: ({tintColor}) => (
<IconButton
icon="add"
size={24}
color={tintColor}
onPress={() => {
navigation.navigate('ManageExpense');
}}
/>
),
})}>
<BottomTabs.Screen
name="RecentExpenses"
component={RecentExpenses}
options={{
title: 'Recent Expenses',
tabBarLabel: 'Recent',
tabBarIcon: ({color, size}) => (
<Ionicons name="hourglass" size={size} color={color} />
),
}}
/>
<BottomTabs.Screen
name="Charts"
component={ChartScreen}
options={{
title: 'Charts',
tabBarLabel: 'Analytics',
tabBarIcon: ({color, size}) => (
<Ionicons name="pie-chart-outline" size={size} color={color} />
),
}}
/>
<BottomTabs.Screen
name="AllExpenses"
component={AllExpenses}
options={{
title: 'All Expenses',
tabBarLabel: 'All Expenses',
tabBarIcon: ({color, size}) => (
<Ionicons name="calendar" size={size} color={color} />
),
}}
/>
</BottomTabs.Navigator>
);
}
function App({ navigation }) {
const expensesCtx = useContext(ExpensesContext);
const { isAuthenticated } = expensesCtx;
const [isFontLoaded, setIsFontLoaded] = useState(false);
const logout = async () => {
try {
// Remove the userToken from AsyncStorage
await AsyncStorage.removeItem('userToken', 'userId');
// await AsyncStorage.removeItem('userId');
await expensesCtx.logoutUser()
} catch (error) {
console.error('Error logging out:', error);
}
};
const checkAuthentication = async () => {
try {
const token = await AsyncStorage.getItem('userToken');
const userId = await AsyncStorage.getItem('userId');
// console.log('App.js checkAuth:', userId);
if (token) {
await expensesCtx.updateIsAuthenticated();
await expensesCtx.set_id(userId);
// console.log('App.js checkAuth:', userId, '_h', token);
await expensesCtx.setExpenses(userId)
}
} catch (error) {
console.error('Error checking authentication:', error);
}
};
useEffect(() => {
checkAuthentication();
}, []);
return (
<>
<StatusBar style="light" />
<NavigationContainer>
<Stack.Navigator
screenOptions={{
headerStyle: {backgroundColor: GlobalStyles.colors.primary500},
headerTintColor: 'white',
}}>
{isAuthenticated ? (
<>
<Stack.Screen
name="ExpensesOverview"
component={ExpensesOverview}
options={{headerShown: false}}
/>
<Stack.Screen
name="ManageExpense"
component={ManageExpense}
options={{
presentation: 'modal',
headerShown: true,
}}
/>
<Stack.Screen
name="DrawerNavigator"
component={MyDrawer}
options={{headerShown: true}}
/>
</>
) : (
<Stack.Screen
name="LoginScreen"
component={LoginScreen}
options={{headerShown: false}}
/>
)}
</Stack.Navigator>
</NavigationContainer>
</>
);
}
export default function AppContext() {
return (
<ExpensesContextProvider>
<App />
</ExpensesContextProvider>
)
}