Open Drawer of child from parent React Navigation V5 - reactjs

I have the main navigation of App setup. Here I initialize route stacks which can have drawer, stacks or tabs stacks.
navigation.js
const Stack = createStackNavigator();
const AppNavigation = () => {
return (
<NavigationContainer style={{backgroundColor: 'red'}}>
<Stack.Navigator headerMode="screen">
<Stack.Screen
options={{headerShown: false}}
name="Auth"
component={AuthRoutes}
/>
<Stack.Screen
name="collector"
component={CollectorRoutes}
/>
</Stack.Navigator>
</NavigationContainer>
);
};
Problem is when I am trying to change the header from one of the stacks , I am not able to change the header settings of that stack like:
collector.routing.js
const Drawer = createDrawerNavigator();
const CollectorRoutes = () => {
return (
<Drawer.Navigator options ={ // options here also have no effect}>
<Drawer.Screen
options={({navigation}) => ({ // this has no effect
headerTitle: props => (
<View>
<Text>HEDAER</Text>
</View>
),
headerLeft: props => (
<Button
block
style={{backgroundColor: themeStyle.textColor}}
onPress={() => navigation.toggleDrawer()}>
<Text>Login</Text>
</Button>
),
})}
name="list"
component={Collector}
/>
</Drawer.Navigator>
);
};
However if I give header options in navigation.js on the scree on CollectorRoutes then the headers are customized.
But then one more problems comes in, I cannot toggle the drawer from there as the navigation stack of child drawer stack is not accessible in parent.
navigation.js -> with header enabled for DrawerStack (CollectorRoutes)
const Stack = createStackNavigator();
const AppNavigation = () => {
return (
<NavigationContainer style={{backgroundColor: 'red'}}>
<Stack.Navigator headerMode="screen">
<Stack.Screen
options={{headerShown: false}}
name="Auth"
component={AuthRoutes}
/>
<Stack.Screen
options={({navigation}) => ({ // this setting gives me header in drawer stack
headerTitle: props => (
<View>
<Text>HEDAER</Text>
</View>
),
headerLeft: props => (
<Button
block
style={{backgroundColor: themeStyle.textColor}}
onPress={() => navigation.toggleDrawer()}> // but here I cannot access drawer toggle
<Text>Login</Text>
</Button>
),
})}
name="collector"
component={CollectorRoutes}
/>
</Stack.Navigator>
</NavigationContainer>
);
};
Is there any solution to access the drawer toggle in the parent? Or can we somehow instead of giving the header option in parent we can configure it in child?

Related

How to pass a value from headerRight component from navigator to nested navigator's screen in react native?

I have my navigators arranged like this.
Bottom Navigator
2. Stack Navigator
3. Material Top Tabs Navigator
4. Home Screen
I have created headerRight button in StackNavigator's ScreenComponent like this.
export default HomeDashboardNavigation = ({route, navigation}) => {
  return (
<DashboardStack.Navigator>
<DashboardStack.Screen name="TopTabNavigator" component={TopTabNavigator}
options={{
headerShown: true,
headerShadowVisible: false,
headerStyle:{backgroundColor:'#FEF8F0'},
headerTitle: (props) => <LogoTitle {...props} />,
headerTitleAlign: 'center',
headerRight: () => (
<TouchableOpacity onPress={() => {
// Need to pass a boolean value to HomeScreen
}}
>
<HintComponent />
</TouchableOpacity>
),
}}
/>
</DashboardStack.Navigator>
)
}
My TopTabNavigator looks like this:
const TopTabNavigator = ({route, navigation}) => {
  return (
<Tab.Navigator tabBar={props => <TabBar {...props} />} >
<Tab.Screen name="HomeScreen" component={HomeScreen} />
<Tab.Screen name="Second" component={SecondScreen} />
<Tab.Screen name="Third" component={ThirdScreen} />
<Tab.Screen name="Fourth" component={FourthScreen} />
</Tab.Navigator>
)
}
My HomeScreen is:
const HomeScreen = (props) => {
const isHeaderRightClicked = false; //Need to get the value here
return (
<ScrollView>
</ScrollView>
);
}
I am using react native navigation 6. I tried using setParams and setting route.params and tried to get is in HomeScreen using useEffect on props.navigation but it never gets executed.
I don't know how to pass value from onPress event to the screen. HomeScreen is embedded in TopTabNavigator which is a Screen component of StackNavigator whose header button click sets the value.
Could you please suggest a good way to do this.
Try something like my example below. Here a useState boolean hook [isClicked, setIsClicked] is defined, where the onPress-function changes this boolean on every press. The state isClicked in then passed through the TopTabNavigator to the HomeScreen where it can be used.
HomeDashboardNavigation file:
export default HomeDashboardNavigation = ({ route, navigation }) => {
const [isClicked, setIsClicked] = useState(false);
return (
<DashboardStack.Navigator>
<DashboardStack.Screen
name="TopTabNavigator"
options={{
headerShown: true,
headerShadowVisible: false,
headerStyle: { backgroundColor: "#FEF8F0" },
headerTitle: (props) => <LogoTitle {...props} />,
headerTitleAlign: "center",
headerRight: () => (
<TouchableOpacity
onPress={() => {
setIsClicked((state) => !state);
}}
>
<HintComponent />
</TouchableOpacity>
),
}}
>
{(props) => <TopTabNavigator isClicked={isClicked} {...props} />}
</DashboardStack.Screen>
</DashboardStack.Navigator>
);
};
TopTabNavigator file
const TopTabNavigator = ({ isClicked, route, navigation }) => {
return (
<Tab.Navigator tabBar={(props) => <TabBar {...props} />}>
<Tab.Screen name="HomeScreen">
{(props) => <HomeScreen isClicked={isClicked} {...props} />}
</Tab.Screen>
<Tab.Screen name="Second" component={SecondScreen} />
<Tab.Screen name="Third" component={ThirdScreen} />
<Tab.Screen name="Fourth" component={FourthScreen} />
</Tab.Navigator>
);
};
HomeScreen file
export const HomeScreen = (props) => {
const isHeaderRightClicked = props.isClicked;
return <ScrollView></ScrollView>;
};

The action 'NAVIGATE' with payload {"name":"Recette"} was not handled by any navigator

I'm trying to navigate between two screens, and i don't know what's happening here.
I would like some help please thanks.
App.js
const Stack = createStackNavigator();
const App = () => {
return (
<NavigationContainer>
<Stack.Navigator
screenOptions={{
headerShown: false
}}
initialRouteName={'Home'}
>
<Stack.Screen options={{headerShown: false}}name="Home" component={Tabs} />
</Stack.Navigator>
</NavigationContainer>
)
}
export default App;`
Home.js
const Home = ({ navigation }) => {
function renderRecetteList() {
const renderItem = ({ item }) => (
<TouchableOpacity
onPress={() => navigation.navigate("Recette" )}
>
</TouchableOpacity>
)
return (
<FlatList
data={recette}
keyExtractor={item => `${item.id}`}
renderItem={renderItem}
contentContainerStyle={{
paddingHorizontal: SIZES.padding * 2,
paddingBottom: 30
}}
/>
)
}
return(
<SafeAreaView style = {styles.container}>
{renderRecetteList()}
</SafeAreaView>
)
}
Recette.js
const Recette = ({ navigation }) => {
return(
<View>
<Text>Search</Text>
</View>
)
}
export default Recette;
The error
The action 'NAVIGATE' with payload {"name":"Recette"} was not handled by any navigator.
Do you have a screen named 'Recette'?
You can only navigate to components that are defined as a Screen in a react Navigator. In order to use navigate to go to Recette you need to define it as a Screen as you did with Home.
const App = () => {
return (
<NavigationContainer>
<Stack.Navigator
screenOptions={{
headerShown: false
}}
initialRouteName={'Home'}
>
...
<Stack.Screen name="Recette" component={Recette} />
</Stack.Navigator>
</NavigationContainer>
)
}

Best practices when using Drawer Navigator with react navigation

I'm building an app with the typical Navigator, which contains a Welcome Screen, Login Screen, Signups, etc. When a user is authenticated, I'm then switching to a Drawer Navigation. However, I'm not entirely sure I'm implementing the Drawer Navigator correctly.
Sample rootNavigator:
const RootNavigator = () => {
const app = useSelector(state => state.app);
const isLoggedIn = app.user ? true : false;
const showWelcome = app.showWelcome;
if (isLoggedIn) {
return (
<Navigator headerMode="none">
<Screen name="Home" component={AppDrawerNavigator} />
</Navigator>
);
}
return (
<Navigator
headerMode="none"
initialRouteName={showWelcome ? 'Walkthrough' : 'Welcome'}>
<Screen name="Walkthrough" component={WalkthroughScreen} />
<Screen name="Welcome" component={WelcomeScreen} />
<Screen name="Login" component={LoginScreen} />
<Screen name="Signup" component={SignupScreen} />
<Screen name="ForgotPassword" component={ForgotPasswordScreen} />
<Screen name="ResetPassword" component={ResetPasswordScreen} />
</Navigator>
);
};
Then, when a user is logged in, I'm returning the following for the AppDrawerNavigator:
const AppDrawerNavigator = () => {
const insets = useSafeAreaInsets();
return (
<SafeAreaView
mode="padding"
style={[
styles.safeArea,
{ paddingTop: insets.top, paddingBottom: insets.bottom },
]}
edges={['right', 'left']}>
<Drawer.Navigator
screenOptions={{
drawerType: 'front',
gestureEnabled: true,
swipeEnabled: false,
}}
drawerContent={props => <HomeDrawer {...props} />}>
{/* Screens */}
<Drawer.Screen name="Landing" component={LandingScreen} />
<Drawer.Screen name="Profile" component={ProfileScreen} />
</Drawer.Navigator>
</SafeAreaView>
);
};
The downside that I'm noticing with this type of implementation is on any of the screens where I try to detect the current view, the route returned is "Home", which appears to be an issue of how I've implemented the drawer.
What is the best way to go about this problem; how I should render the Drawer Navigation?

Drawer's screen component is re-rendering when a drawer opens/closes

I have the main Stack navigator with multiple screens and one of those screens is a nested Drawer navigator with dynamically created screens.
<Provider store={store}>
<NavigationContainer onStateChange={onStateChange}>
<Stack.Navigator initialRouteName="Launch">
<Stack.Screen name="Launch" component={Launch} />
<Stack.Screen name="Login" component={Login} />
<Stack.Screen name="Home" component={Home} />
<Stack.Screen name="ChatBox" component={ChatBox} /> /* <- Here is the drawer navigator screen */
</Stack.Navigator>
</NavigationContainer>
</Provider>
As you can see in the example gif the ChatComponent is always re-rendering when drawer actions were. It does even if the current screen hasn't changed in the drawer's list.
<Drawer.Navigator>
{userChannels.map((channel, index) => {
const chatName = userChannels[index].friendlyName;
const screen = React.memo(() => (
<View style={{ flex: 1 }}>
<Header text={chatName} />
<ChatComponent
channel={channel}
loadEarlier={loadEarlier}
isLoadingEarlier={isLoadingEarlier}
onLoadEarlier={onLoadEarlier}
savedMessages={savedMessages}
onSend={onSend}
isTyping={isTyping}
user={{
_id: me.member.sid || 0,
}}
onInputTextChanged={async () => {
currentChannel &&
(await currentChannel.typing());
}}
enterChannel={() =>
enterChannel(channel.uniqueName)
}
/>
</View>
));
return (
<Drawer.Screen
key={index}
name={chatName || String(index)}
component={screen}
/>
);
})}
</Drawer.Navigator>
The header component is:
export default (props) => {
const { text } = props;
const navigation = useNavigation();
const logout = async () => {
try {
await AsyncStorage.removeItem('accessToken');
await AsyncStorage.removeItem('refreshToken');
} catch (e) {
//
}
navigation.navigate('Login');
};
return (
<View style={styles.header}>
<TouchableOpacity
onPress={() => navigation.dispatch(DrawerActions.openDrawer())}>
<Icon name="menu" color="#FFF" size={18} />
</TouchableOpacity>
{text && <Text style={styles.headerText}>{text}</Text>}
<Text style={styles.headerText} onPress={logout}>
Logout
</Text>
</View>
);
};
The chat component is:
const ChatComponent = React.memo((props) => {
const {
channel,
loadEarlier,
isLoadingEarlier,
onLoadEarlier,
savedMessages,
onSend,
isTyping,
user,
onInputTextChanged,
enterChannel,
} = props;
useEffect(() => {
console.log('render ChatComponent');
enterChannel();
}, []);
return (
<GiftedChat
inverted={false}
loadEarlier={loadEarlier}
isLoadingEarlier={isLoadingEarlier}
onLoadEarlier={onLoadEarlier}
messages={savedMessages}
onSend={onSend}
user={user}
onInputTextChanged={onInputTextChanged}
isTyping={isTyping}
renderBubble={renderBubble}
renderTime={renderTime}
renderInputToolbar={renderInputToolbar}
/>
);
});
Here is the onStateChange of NavigationContainer
How to avoid unnecessary renders of a ChatComponent?

DrawerActions.toggleDrawer() from Parent Navigator

I have a nested navigator structure as follows - the Parent element is a Stack Navigator with a Drawer nested inside. I need to toggle the drawer open from a button in the Stack Navigator.
const Drawer = createDrawerNavigator();
function DrawerNav() {
return (
<Drawer.Navigator initialRouteName="Home">
<Drawer.Screen
name="Main"
component={HomeScreen}
/>
<Drawer.Screen
name="Details"
component={DetailsScreen}
/>
<Drawer.Screen
name="About"
component={AboutScreen}
/>
</Drawer.Navigator
);
}
const Stack = createStackNavigator();
export default function App() {
return (
<NavigationContainer>
<Stack.Navigator
initialRouteName="DrawerNav"
>
<Stack.Screen
name="DrawerNav"
component={DrawerNav}
options={{
headerLeft: () => (
<Icon
style={{ padding: 10 }}
name="menu"
size={30}
color="#900"
onPress={() => navigation.dispatch(DrawerActions.toggleDrawer())}
/>
),
}}
/>
</Stack.Navigator>
</NavigationContainer>
);
}
The current onpress in the Stack Navigator is the closest I got to something that should work to toggle the child drawer. However, adding
const navigation = useNavigation();
Into the App() function creates the following error
Error: Couldn't find a navigation object. Is your component inside a screen in a navigator?\\
In summation, I need to toggle the drawer navigation though a button in its parent. It is clear how to pass navigation actions to children but I am having a difficult time doing it the other way.
In options you need to extract the navigation argument:
const Drawer = createDrawerNavigator();
function DrawerNav() {
return (
<Drawer.Navigator initialRouteName="Home">
<Drawer.Screen
name="Main"
component={HomeScreen}
/>
<Drawer.Screen
name="Details"
component={DetailsScreen}
/>
<Drawer.Screen
name="About"
component={AboutScreen}
/>
</Drawer.Navigator
);
}
const Stack = createStackNavigator();
export default function App() {
return (
<NavigationContainer>
<Stack.Navigator
initialRouteName="DrawerNav"
>
<Stack.Screen
name="DrawerNav"
component={DrawerNav}
options={({ navigation }) => ({
headerLeft: () => (
<Icon
style={{ padding: 10 }}
name="menu"
size={30}
color="#900"
onPress={() => navigation.dispatch(DrawerActions.toggleDrawer())}
/>
),
})}
/>
</Stack.Navigator>
</NavigationContainer>
);
}
DrawerActions must be imported, so
import { NavigationContainer, DrawerActions } from '#react-navigation/native';
should be in your imports, other than that,
onPress={() => this.props.navigation.dispatch(DrawerActions.toggleDrawer())}
does the trick.
I know this is an old topic, but I thought I would offer some clarification.
Read up on Drawer Actions -> https://reactnavigation.org/docs/drawer-actions/
Try this out, i was stuck on the same issue and this worked for me :
<Stack.Screen
name="screen name"
component={ScreenComponent}
options={({ navigation }) => ({
headerRight: (props) => {
return <Button onPress={() => navigation.toggleDrawer() }} />
}
})}
/>
You can toggle Drawer by Button click using the following code-
onPress={() => this.props.navigation.dispatch(DrawerActions.toggleDrawer())}
`
options can take a function and provide arguments for you, one of which is navigation. Here's the code:
options={({navigation}) => {
headerLeft: () => (
<Icon
style={{ padding: 10 }}
name="menu"
size={30}
color="#900"
onPress={() => navigation.dispatch(DrawerActions.toggleDrawer())}
/>
),
}}
Hope this helps.

Resources