How to pass parameters between two screens in react native? - reactjs

Please, do not hurry to close this question as duplicate. I have read the official documentation here and so fa I have managed to pass successfully parameters between screens. There is som problem with my current case, and the solution is not obvious for me.
I have two screens. The first is called "Feed" and it belongs to bottom tab navigator. From the "Feed" screen I want to be able to navigate to "Comments" screen which is part of stack navigator.
I am able to successfully navigate between both screens, however, for some reason I am not able to pass any parameters when I navigate from "Feed" to "Comments".
Part of my "Feed" screen where I press "comments" icon:
<TouchableOpacity
onPress={() => {
console.log("passing param id: ", id);
navigation.navigate("Comments", { id: id });
}}
>
<FontAwesome name="comment-o" size={30} color="#0047AB" />
</TouchableOpacity>
What I see on the console:
passing param id: some_id_whic_does_not_matter
I manage to successfully navigate to my "Comments" screen. Part of my "Comments" screen:
const CommentsScreen = (props) => {
console.log("Comments Screen: ");
console.log("comments: ", props);
What I see on the console for the route object:
route": Object {
"key": "Comments-some-key",
"name": "Comments",
"params": undefined,
}
Can you tell me what I am doing wrong?
Where I am currently located on NetworkNavigator tab, and from there I want to navigate to the Comments screen.
I still dont see where the issue is?

I think we access params via route I mean
const CommentsScreen =({route})=>{
const {id} = route.params;
console.log(id);
...}

You can access your passed params via following
const CommentsScreen = (props) => {
console.log("Comments Screen: ");
console.log("comments: ", props.route.params.id);
props.route.params
It contains the object you passed in
navigation.navigation('screenname', {
...this object...
});
you can extract the data by using the same keys.
Example
navigation.navigation('screenname', {
id: '1',
name: 'Adam',
age: 12
});
//Then you can access via
console.log("id: ", props.route.params.id);
console.log("name: ", props.route.params.name);
console.log("age: ", props.route.params.age);

I found the answer to my question here: https://reactnavigation.org/docs/5.x/nesting-navigators#passing-params-to-a-screen-in-a-nested-navigator
So in my case:
navigation.navigate("Root Navigator", {
screen: "Comments",
params: { id: id },
});

Related

React-navigation crash when going back from a nested navigation

I'm using React-navigation 6 on my React native application
I have two StackNavigator in a BottomStackNavigator with those screens and params on each stack :
export type BottomNavigationStack = {
Planner: undefined,
Profile: undefined,
}
export type PlannerNavigationStack = {
PlannerHome: undefined,
PlannerDetails: {
detail: { ... }
},
Profile: {
screen: string,
initial?: boolean,
params?: { [name: string]: string }
}, // We can go to ProfileNavigationStack from PlannerNavigationStack
}
export type PlannerNavDetailProps = StackScreenProps<PlannerNavigationStack, 'PlannerDetails'>
export type ProfileNavigationStack = {
ProfileHome: undefined,
ProfileAddThings: {
service: string
from: string,
},
Planner: {
screen: string,
initial?: boolean,
params?: { [name: string]: string }
}, // We can go to PlannerNavigationStack from ProfileNavigationStack
}
export type ProfileNavAddThingsProps = StackScreenProps<ProfileNavigationStack, 'ProfileAddThings'>
So i'm trying to navigate from PlannerStack to ProfileStack, when I'm in ProfileStack, I want to go back to PlannerStack.
I got no problem going to ProfileStack from PlannerStack like this :
navigation.navigate('Profile', {
screen: 'ProfileAddThings',
params: {
service: 'myservice',
from: 'myfrom',
},
initial: false,
})
So at that moment I'm on ProfileStack on screen ProfileAddThings.
if I read the documentation, the PlannerStack keep it's history, and if I click on the bottomTab button of Planner I can see the page is still PlannerDetails.
But, when I click on the back button of the screen ProfileAddThings, I'm going back to ProfileHome.
I tried to overide the backButton action of the screen ProfileAddThings with that code :
navigation.navigate('Planner', { screen: 'PlannerDetails' })
But I got the error : undefined is not an object (evaluating 'route.params.detail')
Detail is a parameter of PlannerDetails screen.
I really don't understand why this parameter is undefined because de PlannerStack history is still present.
Someone has already gone back from a nested navigation in react native ?
Thanks
You need to get the parent navigator.
You can do this by using the getParent() method from the current screen.
Will be something like this:
let parent = navigation.getParent(); // This will assing PlannerStack to the parent variable.
then you should call navigation method with the Route name
parent.navigate('PlannerDetails');
The route history, register the hole component from the screen (in your case PlannerStack) and in your PlannerStack the innitialRoute probably is ProfileHome, thats the reason you get there when using the back button.

State updating only after second onPress

I'm building a React Native app which renders a flatlist of groups. By pressing on one group, you get to see all the individual items nested within.
I'm using the following this navigation library and I'm passing props as such:
const navigateToDetails = useCallback(() => {
Navigation.push(props.componentId, {
component: {
name: pagesNames.DETAILS,
passProps: {
groupId: selectedGroup,
},
},
});
});
All of this is within a functional component which has a couple more relevant things, to begin with we have the state and function that gets us selectedGroup:
const [selectedGroup, setSelectedGroup] = useState(Number);
function handleTouch(value) {
setSelectedGroup(value);
console.log('value: ' + value);
navigateToDetails(value);
}
value is taken from an item in a Flatlist which gets its data from this fake API.
Value is retrieved from the list in this fashion:
<FlatList
data={data}
renderItem={({ item }) => (
<TouchableOpacity
style={[styles.card, { width: windowWidth - 30 }]}
onPress={() => handleTouch(item.id)}>
{misc components}
</TouchableOpacity>
)}
/>
Expected Behaviour
I'm expecting that when I tap an item from the list, it'd navigate to the Details page and populate it with its data. However this does not happen. Upon logging out value and selectedGroup, I find that value is always logged out correctly but selectedGroup is always first logged out as 0.
Only after a second press does it show the expected id and populate the Details page.
You are redirected before state is set. initialize selectedGroup with null/undefined/0
try navigating in useEffect
function handleTouch(value) {
setSelectedGroup(value);
console.log('value: ' + value);
}
useEffect(() => {
if (selectedGroup) {
navigateToDetails(selectedGroup)
}
}, [selectedGroup, navigateToDetails])

FetchMore : Request executed twice every time

I am trying to implement pagination in a comment section.
I have normal visual behavior on the website. When I click the get more button, 10 new comments are added.
My problem is the request is executed twice every time. I have no idea why. The first time, it is executed with a cursor value, the second time without it. It seems that the useQuery hook is executed after each fetchMore.
Any help would be appreciated. Thanks!
component :
export default ({ event }) => {
const { data: moreCommentsData, fetchMore } = useQuery(getMoreCommentsQuery, {
variables: {
id: event.id,
},
fetchPolicy: "cache-and-network",
});
const getMoreComments = () => {
const cursor =
moreCommentsData.event.comments[
moreCommentsData.event.comments.length - 1
];
fetchMore({
variables: {
id: event.id,
cursor: cursor.id,
},
updateQuery: (prev, { fetchMoreResult, ...rest }) => {
return {
...fetchMoreResult,
event: {
...fetchMoreResult.event,
comments: [
...prev.event.comments,
...fetchMoreResult.event.comments,
],
commentCount: fetchMoreResult.event.commentCount,
},
};
},
});
};
return (
<Container>
{moreCommentsData &&
moreCommentsData.event &&
moreCommentsData.event.comments
? moreCommentsData.event.comments.map((c) => c.text + " ")
: ""}
<Button content="Load More" basic onClick={() => getMoreComments()} />
</Container>
);
};
query :
const getMoreCommentsQuery = gql`
query($id: ID, $cursor: ID) {
event(id: $id) {
id
comments(cursor: $cursor) {
id
text
author {
id
displayName
photoURL
}
}
}
}
`;
Adding
nextFetchPolicy: "cache-first"
to the useQuery hook prevents making a server call when the component re renders.
This solved my problem.
Could it be that the second request you are seeing is just because of refetchOnWindowFocus, because that happens a lot...
I had a similar problem and I solved it by changing the fetchPolicy to network-only.
Please note, if you are hitting this issue with #apollo/client v3.5.x there was a bug related to this which was fixed from v3.6.0+. That commit solved the duplicated execution issue in my case.
It only makes one request if you are worried about that but the react component refreshes but not rerender each time an item in the useQuery hook changes.
Take for instance it would refresh the component when loading changes, making it seem like it sends multiple requests.

"console.error" the action "NAVIAGTE" with payload was not handled by any navigator

after implementing nesting navigator I am getting error in dishdetail it is not getting render I followed documentation and tried this
onPress = {
() => navigate({
name: 'Dishdetail',
params: {
screen: "Dishi",
dishId: item.id
}
})
}
but still error.
You Navigate function should be like this.
onPress={()=>this.props.navigation.navigate('Dishdetail', { screen: "Dishi" , dishId: item.id })}
According to your provided code, you didn't define any route with the name of Dishdetail, So change your Dish detail to Dishdetail like below.
<Homenavigator.Screen
name="Dishdetail"
component={Home}
/>

Passing function as a param in react-navigation 5

NOTE: This query is for react-navigation 5.
In react navigation 4 we could pass a function as a param while navigating but in react navigation 5, it throws a warning about serializing params.
Basically, what I am trying to do is, navigate to a child screen from parent screen, get a new value and update the state of the parent screen.
Following is the way I am currently implementing:
Parent Screen
_onSelectCountry = (data) => {
this.setState(data);
};
.
.
.
<TouchableOpacity
style={ styles.countrySelector }
activeOpacity={ 0.7 }
onPress={ () => Navigation.navigate("CountrySelect",
{
onSelect: this._onSelectCountry,
countryCode: this.state.country_code,
})
}
>
.
.
.
</TouchableOpacity>
Child Screen
_onPress = (country, country_code, calling_code) => {
const { navigation, route } = this.props;
navigation.goBack();
route.params.onSelect({
country_name: country,
country_code: country_code,
calling_code: calling_code
});
};
Passing a callback through react native navigation params is not recommended, this may cause the state to freeze (to not to update correctly). The better solution here would be using an EventEmitter, so the callback stays in the Screen1 and is called whenever the Screen2 emits an event.
Screen 1 code :
import {DeviceEventEmitter} from "react-native"
DeviceEventEmitter.addListener("event.testEvent", (eventData) =>
callbackYouWantedToPass(eventData)));
Screen 2 code:
import {DeviceEventEmitter} from "react-native"
DeviceEventEmitter.emit("event.testEvent", {eventData});
useEffect(() => {
return () => {
DeviceEventEmitter.removeAllListeners("event. testEvent")
};
}, []);
Instead of passing the onSelect function in params, you can use navigate to pass data back to the previous screen:
// `CountrySelect` screen
_onPress = (country, country_code, calling_code) => {
const { navigation, route } = this.props;
navigation.navigate('NameOfThePreviousScreen', {
selection: {
country_name: country,
country_code: country_code,
calling_code: calling_code
}
});
};
Then, you can handle this in your first screen (in componentDidUpdate or useEffect):
componentDidUpdate(prevProps) {
if (prevProps.route.params?.selection !== this.props.route.params?.selection) {
const result = this.props.route.params?.selection;
this._onSelectCountry(result);
}
}
There is a case when you have to pass a function as a param to a screen.
For example, you have a second (independent) NavigationContainer that is rendered inside a Modal, and you have to hide (dismiss) the Modal component when you press Done inside a certain screen.
The only solution I see for the moment is to put everything inside a Context.Provider then use Context.Consumer in the screen to call the instance method hide() of Modal.

Resources