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

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?

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>
)
}

How to Pass and Read param in top bar nested navigators?

const TopTab = createMaterialTopTabNavigator();
export const TopNavigator = () => {
return (
<TopTab.Navigator>
<TopTab.Screen name="profile" component={profile} />
<TopTab.Screen name="setting" component={setting} />
</TopTab.Navigator>
);
}
const MainMenuStackNavigator = createStackNavigator();
export const MainMenuNavigator = () => {
return (
<MainMenuStackNavigator.Navigator screenOptions={defaultNavOptions}>
<MainMenuStackNavigator.Screen name="home" component={homeScreen}/>
<MainMenuStackNavigator.Screen name="tab" component={TopNavigator }/>
</MainMenuStackNavigator.Navigator>
);
};
function HomeScreen() {
return (
<View>
<Text>Home!</Text>
<Button
title="Go to Top Navigator Screen"
onPress={() => {props.navigation.navigate({name: "tab",params:{data: "Testing"}});}}
/>;
</View>
);
}
export default function App() {
return (
<NavigationContainer>
< MainMenuNavigator />
</NavigationContainer>
);
}
Need to pass Data from 'home' screen to both 'Profile' and 'Setting' screen
i know how pass data from home screen to any one of those screen,
props.navigation.navigate(tab, {
screen: "profile",
params: { data: 'testing'}
});
but here my question is ' how to send data to both screens(profile and setting)?

Unable to navigate to drawer stack in react navigation 5

I have an app that has a bottom tab navigator inside a drawer navigator as my Home Screen and I also have a stack navigator comprising of my auth screens (including login screen).
I am unable to navigate from my login screen to my Home stack screen after a successful login.
I get the error - the action navigate with payload was not handled by any navigator.
Please see my code. How can I resolve this
Navigation file - Navigation.js
const MainTabScreen = () => (
<Tab.Navigator
initialRouteName="Main"
activeColor="#fff"
>
<Tab.Screen
name="Main"
component={Main}
options={{
tabBarLabel: 'Home',
tabBarColor: "#009387",
tabBarIcon: ({ color }) => (
<Icon name="md-home" color={color} size={26} />
),
}}
/>
<Tab.Screen
name="Profile"
component={ProfileScreen}
options={{
tabBarLabel: 'Profile',
tabBarColor: "#694fad",
tabBarIcon: ({ color }) => (
<Icon name="md-contact" color={color} size={26} />
),
}}
/>
<Tab.Screen
name="Explore"
component={ExploreScreen}
options={{
tabBarLabel: 'Account',
tabBarColor: "#d02860",
tabBarIcon: ({ color }) => (
<Icon name="md-planet" color={color} size={26} />
),
}}
/>
</Tab.Navigator>
)
// HOME STACK SCREEN
const DrawerScreen = () => (
<Drawer.Navigator drawerContent={props => <DrawerContent {...props} /> } >
<Drawer.Screen name='MainTab' component={MainTabScreen} />
</Drawer.Navigator>
);
const AuthStack = createStackNavigator();
const AuthStackScreen = ({navigation}) => (
<AuthStack.Navigator headerMode='none'>
<AuthStack.Screen name="SplashScreen" component={SplashScreen} />
<AuthStack.Screen name="SignInScreen" component={SignInScreen} /> // LOGIN SCREEN
<AuthStack.Screen name="SignUpScreen" component={SignUpScreen} />
</AuthStack.Navigator>
);
const RootStack = createStackNavigator();
const RootStackScreen = () => {
const [isLoading, setIsLoading] = React.useState(true);
const [user, setUser] = React.useState(null);
React.useEffect(() => {
// Fetch the token from storage then navigate to our appropriate place
const bootstrapAsync = async () => {
let userToken;
try {
userToken = await AsyncStorage.getItem('FBIdToken');
console.log("toen", userToken)
if(userToken !== null) {
setIsLoading(!isLoading);
setUser(userToken);
} else {
setIsLoading(!isLoading);
}
} catch (e) {
console.log(error)
}
// After restoring token, we may need to validate it in production apps
};
bootstrapAsync();
}, []);
return (
<RootStack.Navigator
headerMode="none"
screenOptions={{ animationEnabled: false }}
mode="modal"
>
{isLoading ? (
<RootStack.Screen name="Loading" component={LoadingScreen} />
) : user ? (
<RootStack.Screen name="DrawerScreen" component={DrawerScreen} /> //HOME STACK ) : (
<RootStack.Screen name="AuthStackScreen" component={AuthStackScreen} />
)}
</RootStack.Navigator>
);
};
export default RootStackScreen;
App.js
<Provider store={store}>
<NavigationContainer ref={navigationRef}>
<RootStackScreen />
</NavigationContainer>
</Provider>
Login.js
loginHandle = () => {
this.props.navigation.navigate("DrawerScreen")
};
I suspect that in your RootStackScreen component, your condition isLoading is true so the route to your DrawerScreen does not exists when you navigate. Try removing the condition to be sure. Then if this isthe reason, yu could try applying your condition to the component you pass to your DrawerScreen instead, something like :
<RootStack.Screen name="DrawerScreen" component={isLoading ? LoadingScreen : DrawerScreen} />

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