Passing state to parent and then to child - reactjs

I have app.js which assigns values to a state. I am then trying to pass that state to a Home screen which renders a Screen1. I need this state in Screen1.
You can see I am trying to display the users latitude and longitude on Screen1 but I am not able to render anything.
I have listed the code below as well as in this snack demo here. The goal is for the state to go from App.js -> Home.js -> Screen1.js
EDIT:: I might use React native context but never have before. If the current way I am doing it is considered a poor practice then context is the way to go.
Home.js
export default function Home({ navigation, latitude, longitude }) {
return (
<View style={styles.container}>
<Screen1 />
</View>
);
}
Screen1.js
export default class Screen1 extends React.Component {
render() {
return (
<View style={styles.container}>
<Text style={styles.paragraph}>latitude:</Text>
<Text style={styles.paragraph}>{this.props.latitude}</Text>
<Text style={styles.paragraph}>longitude:</Text>
<Text style={styles.paragraph}>{this.props.longitude}</Text>
</View>
);
}
}
export default function MyTabs() {
const [latitude, setLatitude] = useState(null);
const [longitude, setLongitude] = useState(null);
const permissionAlert = () => {
Alert.alert(
'You did not allow location permissions',
'Please go to settings and allow location permissions for full functionality',
[
],
{
cancelable: true
}
);
}
useEffect(() => {
(async () => {
let { status } = await Location.requestForegroundPermissionsAsync();
if (status !== 'granted') {
permissionAlert();
return;
}
let location = await Location.getCurrentPositionAsync({});
setLatitude(location.coords.latitude)
setLongitude(location.coords.longitude);
})();
}, []);
console.warn("latitude: ", latitude);
console.warn("longitude: ", longitude);
return (
<NavigationContainer>
<Stack.Navigator
initialRouteName="Home">
<Stack.Screen name="Home" options={{headerShown: false}}>
{(props) => <Home {...props} latitude={latitude} longitude={longitude} />}
</Stack.Screen>
<Stack.Screen name="Screen1" options={{headerShown: false}}>
{(props) => <Screen1 {...props} latitude={latitude} longitude={longitude} />}
</Stack.Screen>
</Stack.Navigator>
</NavigationContainer>
);
}

You need to pass latitude/longitude from MyTabs -> Home -> Screen1. Currently, you're setting them on Home, but not on Screen1. To fix, add the props to <Screen1 /> in Home.js
export default function Home({ navigation, latitude, longitude }) {
return (
<View style={styles.container}>
<Screen1 latitude={latitude} longitude={longitude} />
</View>
);
}
It's also worth noting that you're rendering Screen1 both
directly within MyTabs
indirectly in MyTabs, when you render Home
Basically, make sure you always pass all expected props when you render a component and you should be good to go!

Related

Swipeable component doesn't work in BottomTabNavigator

I have a FlatList inside of a screen that uses BottomTabNavigator from react-navigation-bottom-tabs. Inside of the FlatList are swipeable items, using Swipeable from react-native-gesture-handler. Inside of the tab screens these items aren't swipeable for some reason. However, when I put the screen with the FlatList inside of a StackNavigator screen, and nest that inside of the BottomNavigator tabscreen, it does work. I'm at a loss for why it works this way but not the other way. Does anyone have the slightest clue why this is the case?
Edit: I've seen the issue of not being able to navigate bottom tabs using swipe. I'm not trying to navigate tabs though. Swipeable is applied to items inside a FlatList, that is inside its own component, that is rendered by the Home screen, which in turn is used by the navigator.
The TabsNavigator:
export default function TabsNavigator() {
const Tab = createBottomTabNavigator();
return (
<Tab.Navigator screenOptions={{ headerShown: false }}>
<Tab.Screen
name="List"
component={HomeStack}
options={{
tabBarIcon: ({ focused }) => (
<FontAwesomeIcon
icon={faList}
style={{ color: focused ? "#104543" : "#CCC" }}
/>
),
tabBarActiveTintColor: "#104543",
}}
/>
)
}
The HomeStack (StackNavigator):
export default function StackNavigator() {
const Stack = createStackNavigator();
return (
<Stack.Navigator>
<Stack.Screen
options={{ headerShown: false }}
name="Home"
component={Home}
/>
</Stack.Navigator>
);
}
The Home screen:
export const Home = ({ navigation }) => {
const dispatch = useDispatch();
useEffect(() => {
const fetchUserData = async () => {
const data = await axios.get(
`http://10.0.2.2:80/
`,
{
auth: {
username: "***",
password: "***",
},
}
);
dispatch(setUsersData(data.data));
};
return navigation.addListener("focus", async () => {
await fetchUserData();
});
}, [navigation]);
return (
<View style={styles.container}>
<SearchBarHeader />
//The component with the FlatList in question that contains swipeable items
<SwipeableFlastList />
</View>
);
};
The preferred outcome would be to place the Home screen inside of the Tab.Screen component like this:
<Tab.Screen
name="List"
component={Home}
/>
Per request, the Swipeable component. It's inside of another ListItem component, which inturn is inside a component that renders the flatlist:
<KeyboardAvoidingView style={styles.container}>
<Swipeable
renderRightActions={(
progress: Animated.AnimatedInterpolation,
dragAnimatedValue: Animated.AnimatedInterpolation
) => renderRightActions(progress, dragAnimatedValue, item.Id)}
renderLeftActions={(
progress: Animated.AnimatedInterpolation,
dragAnimatedValue: Animated.AnimatedInterpolation
) => renderLeftActions(progress, dragAnimatedValue, item.Id)}
useNativeAnimations={true}
onSwipeableOpen={() => setSwipeOpen(!swipeOpen)}
onSwipeableClose={() => setSwipeOpen(!swipeOpen)}
>
<TouchableWithoutFeedback>
<AccordionListItem item={item} />
</TouchableWithoutFeedback>
</Swipeable>
</KeyboardAvoidingView>

My component render in App and in Navigator are differents

I am developing an app in React Native on Expo during few weeks now.
In my App.tsx i am doing this :
const App = () => {
const [producteurs, setSearchProducteurs] = useState('');
useEffect(() => {
sql.initDB();
sql.insertInDB();
sql.search().then((data: any)=>{
setSearchProducteurs(data)
}).catch((error) => console.log(error));
})
const isLoadingComplete = useCachedResources();
const colorScheme = useColorScheme();
const Stack = createNativeStackNavigator<RootStackParamList>();
if (!isLoadingComplete) {
return null;
} else {
return (
<SafeAreaProvider>
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen
name="Root"
component={Map}
options={{ title: 'My home' }}
/>
</Stack.Navigator>
</NavigationContainer>
<Map company_name={producteurs} />
<StatusBar />
</SafeAreaProvider>
);
}
}
But my Map component rendering twice on my screen. I have a header with "My home" and an empty map below, and below this map, i have a second map but the right one, with data fetch in my App.tsx.
Do you know why this behaviour is like that ?
If i remove all my NavigationContainer, i have only my component which is good, with data. But i have no more my Navigation..
Thanks in advance for help

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

React Native Navigate to Particular Screen When Notification is Clicked

I am trying to implement navigation upon the user click on a notification that they have received. I am successfully receiving the notifications with expo-notifications and accept data (routes) from API but unable to navigate to another screen when user clicks on a notification.
useNotifications:
export default useNotifications = () => {
...
useEffect(() => {
registerForPushNotificationsAsync().then((token) => {
setExpoPushToken(token);
alert(token);
});
notificationListener.current = Notifications.addNotificationReceivedListener(
(notification) => {
setNotification(notification);
console.log(notification);
}
);
responseListener.current = Notifications.addNotificationResponseReceivedListener(
(response) => {
//notification is received OK
console.log("opened");
//here I want to navigate to another screen using rootnavigation
navigation.navigate("Account");
//alert shows fine
alert("ok");
}
);
return () => {
Notifications.removeNotificationSubscription(notificationListener);
Notifications.removeNotificationSubscription(responseListener);
};
}, []);
};
navigator:
const SettingsNavigation = ({ component }) => {
useNotifications();
return (
<Stack.Navigator mode="card" screenOptions={{ headerShown: false }}>
<Stack.Screen
name="Main"
component={component}
options={{ title: "Home" }}
/>
<Stack.Screen
name="Timetable"
component={TimetableScreenBoss}
options={menuOptions("Schedule")}
/>
<Stack.Screen
name="Account"
component={AccountNavigator}
options={menuOptions("Account", false)}
/>
</Stack.Navigator>
);
};
root navigation:
import React from "react";
export const navigationRef = React.createRef();
const navigate = (name, params) =>
navigationRef.current?.navigate(name, params);
export default {
navigate,
};
app.js:
import { navigationRef } from "./app/navigation/rootNavigation"; //rootnavigation
<NavigationContainer navigationRef={navigationRef}>
<CustomNavigator>
</NavigationContainer>
Assuming you are using react-navigation, the NavigationContainer accepts a normal ref prop:
<NavigationContainer ref={navigationRef}>
<CustomNavigator>
</NavigationContainer>
see NavigationContainer docs

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?

Resources