I'm trying to implement a single drawer whose drawer position and content can be dynamically changed.
I have a drawer navigator with a stack navigator inside. The header of the stack navigator has two buttons. The left button sets the drawerPosition
to "left"
and calls navigations.openDrawer()
and the right button sets the drawerPosition
to "right"
and calls navigation.openDrawer()
.
My current implementation looks like this:
const Stack = createStackNavigator();
const Drawer = createDrawerNavigator();
const DrawerPositionContext = React.createContext([{}, () => {}]);
const DrawerPositionProvider = (props) => {
const [drawerPosition, setDrawerPosition] = useState({});
return (
<DrawerPositionContext.Provider value={[drawerPosition, setDrawerPosition]}>
{props.children}
</DrawerPositionContext.Provider>
);
};
function CustomDrawerContent(props) {
return (
<DrawerContentScrollView {...props}>
{props.drawerPosition === 'left' ? (
<DrawerItem label="Left" />
) : (
<DrawerItem label="Right" />
)}
</DrawerContentScrollView>
);
}
const Screen1 = () => {
return <Text>Screen1</Text>;
};
const useDrawerPosition = () => {
const [drawerPosition, setDrawerPosition] = React.useContext(
DrawerPositionContext
);
return {
drawerPosition,
setDrawerPosition
};
};
const DrawerNavigator = () => {
const { drawerPosition, setDrawerPosition } = useDrawerPosition();
return (
<Drawer.Navigator
drawerPosition={drawerPosition}
drawerContent={(props) => (
<CustomDrawerContent {...props} drawerPosition={drawerPosition} />
)}
>
<Drawer.Screen name="stack navigator" component={StackNavigator} />
</Drawer.Navigator>
);
};
const StackNavigator = ({ navigation }) => {
const { setDrawerPosition } = useDrawerPosition();
return (
<Stack.Navigator
screenOptions={{
headerLeft: () => (
<Button
title="left"
onPress={() => {
setDrawerPosition("left");
navigation.openDrawer();
}}
/>
),
headerRight: () => (
<Button
title="right"
onPress={() => {
setDrawerPosition("right");
navigation.openDrawer();
}}
/>
)
}}
>
<Stack.Screen name="screen1" component={Screen1} />
</Stack.Navigator>
);
};
export default function App() {
return (
<DrawerPositionProvider>
<NavigationContainer>
<DrawerNavigator />
</NavigationContainer>
</DrawerPositionProvider>
);
}
So I use react context to share and update the current drawer position.
The behavior I'm experiencing is that opening the left drawer will always open the drawer correctly, but opening the right drawer will not open the drawer correctly most of the time. Instead of the drawer I only see the backdrop shadow.
My first guess was that the context isn't updated before the drawer is opened, but converting the components to class-based components and using a setState
callback gave the same result, so I'm not sure what is happening here.
I know the usual implementation for doing something like this is to create two drawers nested in a certain way, but it is it possible to do it with the approach I've tried?
Update
I think this is a bug. The problem seems to be inside Drawer.tsx
(https://github.com/react-navigation/react-navigation/blob/main/packages/drawer/src/views/Drawer.tsx).
I'm not that familiar with Animated
(https://reactnative.dev/docs/animated), but I think the problem is this code in componentDidUpdate
:
if (prevProps.drawerPosition !== drawerPosition) {
this.drawerPosition.setValue(
drawerPosition === 'right' ? DIRECTION_RIGHT : DIRECTION_LEFT,
);
}
From messing around with the snack you linked, it seems like the context is actually updated. The problem only appears if you switch from "right" to "left". (This is somewhat supported by what I saw in the inspector, but I didn't dig deep).
See this updated snack that logs the position as it updates/bubbles and defaults to the "right" position. If you click the right button first, you can see it working, but if you click left it will break.
All in all, this is a reactnavigation bug IMHO. This is a problem that only seems to appear with drawerType="front"
, so as a workaround you could try adding a drawerType="back"
prop to Drawer.Navigator
.