Reputation: 2045
I've a page that will render the user's name if s/he is logged in or "Create an account" or "Sign in" option if s/he not. Screen as below
They can navigate to "Sign in" or "Create an account" page. After successfully signing in or registering, it will navigate to this page and it will show the user name. Screen as below
Currently, I store user data in AsyncStorage
, I want to update this field once the user successfully logs in or register when they redirect from the page.
How can I achieve this?
is there a way to pass param from navigate.goBack()
and parent can listen to the params and update its state?
Upvotes: 102
Views: 208068
Reputation: 1
I also spent a long time looking for a practical solution for this. In the end, it was Michael Mao's solution. I just modified it a little, as I often work with the useNavigation() hook (tested with react-navigation 6.x) :
export const goBackWithParams = (navigation, params) => {
if (!params) {
navigation.goBack();
return;
}
navigation.dispatch(state => {
if (state.routes.length >= 2) {
const prevRoute = state.routes[state.routes.length - 2];
return CommonActions.navigate({
name: prevRoute.name,
params: params,
merge: true,
});
} else {
return CommonActions.goBack();
}
});
};
Upvotes: 0
Reputation: 164
navigation.dispatch(state => {
const prevRoute = state.routes[state.routes.length - 2];
return CommonActions.navigate({
name: prevRoute.name,
params: {},
merge: true,
});
});
Upvotes: 1
Reputation: 463
/**
* 返回上一页携带参数, 不需要传递 screen name
*
* react-native-navigation 6.x
* https://reactnavigation.org/docs/navigation-actions/
* https://stackoverflow.com/a/76581949/4348530
* @param params
*/
export const goBack = params => {
const navigation = navigationRef.current;
if (navigation?.isReady()) {
if (!params) {
navigation.goBack();
} else {
navigation.dispatch(state => {
if (state.routes.length >= 2) {
const prevRoute = state.routes[state.routes.length - 2];
return CommonActions.navigate({
name: prevRoute.name,
params: params,
merge: true,
});
} else {
return CommonActions.goBack();
}
});
}
}
};
Upvotes: 0
Reputation: 116
For those trying to tackle this issue, react-navigation
provides a neat solution, especially from version 6 onwards.
check this example:
In ScreenA
(from where you navigate to ScreenB
):
When you navigate to ScreenB
, you don't need to pass anything special.
navigation.navigate('ScreenB');
In ScreenB
(where you collect/generate the data to pass back):
After obtaining the necessary data, you can pass it back to ScreenA
using:
const someData = "Your Data"; // This can be any data you wish to pass back
navigation.navigate('ScreenA', { dataFromB: someData });
Back in ScreenA
:
Listen for the data passed from ScreenB
:
useEffect(() => {
if (route.params?.dataFromB) {
// Use the data here as required
console.log(route.params.dataFromB);
}
}, [route.params?.dataFromB]);
As highlighted in the react-navigation
docs:
Params can transfer data not only to a new screen but also back to a previous one. If a screen is already in the stack, the
navigate
method acts likegoBack
with the added ability to pass parameters.
For other ways to share data:
navigation.setParams()
to pass a callback function, then call it in ScreenB
to send data back.useContext
for shared data between screens.All these methods are valid, but I think it depends mainly on the needs of each one, but definitely the least complex and easiest to maintain is using navigate.navigate()
.
Upvotes: 0
Reputation: 5065
in the latest version of react navigation (v6.x), you can use navigate
as normal instead of goBack
if you want to pass params while going back (as navigate keeps the stack).
navigation.navigate('screenname', {param: value});
if the screenname
screen is in the stack and is the previos screen as compared to the current screen, then navigate will act as goBack.
More details here
Upvotes: 3
Reputation: 3814
With react-navigation
version 6 I implemented it by passing a parameter while navigating to another page then calling that parameter which is function beside goBack()
function like this
Screen A :
navigation.navigate("Screen B", { refresh: () => getData() })
Screen B :
const { refresh } = route.params;
navigation.goBack();
refresh();
Upvotes: 1
Reputation: 3496
If you are using redux
you can create an action to store data and check the value in parent Component
or you can use AsyncStorage
.
But I think it's better to passing only JSON-serializable
params, because if someday you want to save state of navigation, its not very easy.
Also note react-navigation
has this feature in experimental
https://reactnavigation.org/docs/en/state-persistence.html
Each param, route, and navigation state must be fully JSON-serializable for this feature to work. This means that your routes and params must contain no functions, class instances, or recursive data structures.
I like this feature in Development Mode and when I pass params as function I simply can't use it
Upvotes: 2
Reputation: 179
Passing a callback through React Navigation in v5 throws a warning:
This can break usage such as persisting and restoring state
You can execute some code in screen A when you navigate back to it from Screen B in two easy ways:
First:
useEffect(() => {
const willFocusSubscription = navigation.addListener("focus", () => handleRefresh());
return () => willFocusSubscription
}, []);
This gets the job done. However, this method will be executed every time the screen is rendered. In order to only render it once when navigating back you can do the following:
Screen A:
import { DeviceEventEmitter } from "react-native";
useEffect(() => {
DeviceEventEmitter.addListener("userIsGoingBack", () => handleRefresh());
return () => DeviceEventEmitter.removeAllListeners("listingCreated");
}, []);
Screen B:
import { DeviceEventEmitter } from "react-native";
DeviceEventEmitter.emit("userIsGoingBack");
You can also pass some data alongside the emitted event to use in screen A if needed.
Upvotes: 2
Reputation: 820
I could not get any answer to work for my specific use case. I have a list being fetched from a database and a screen to add another list item. I wanted that once a user creates the item on the second screen, the app should navigate back to the first screen and show the newly added item in the list. Although the item was being added in the database, the list was not updating to reflect the change. The solution that worked for me: https://github.com/react-navigation/react-navigation.github.io/issues/191#issuecomment-641018588
So all I did was put this on the first screen and now the useEffect is triggered every time the screen is in focus or loses focus. import { useIsFocused } from "@react-navigation/native";
const isFocused = useIsFocused();
useEffect(() => {
// Code to run everytime the screen is in focus
}, [isFocused]);
Upvotes: 0
Reputation: 81
Easiest way to render the required components is by using useIsFocused hook.
React Navigation provides a hook that returns a boolean indicating whether the screen is focused or not. The hook will return true when the screen is focused and false when our component is no longer focused.
First import this in the required page where you want to navigate back.
import { useIsFocused } from '@react-navigation/native';
Then, store this in any variable, and render components changes using React useEffect hook.
See code below or visit: Here
import React, { useState } from 'react';
import { useIsFocused } from '@react-navigation/core';
const HomeScreen = () => {
const isFocused = useIsFocused();
useEffect(()=>{
console.log("Focused: ", isFocused); //called whenever isFocused changes
}, [isFocused]);
return (
<View>
<Text> This is home screen! </Text>
</View>
)
}
export default HomeScreen;
Upvotes: 8
Reputation: 41
This solution did it for me, according to the navigation site: https://reactnavigation.org/docs/function-after-focusing-screen/#re-rendering-screen-with-the-useisfocused-hook
import { useFocusEffect } from '@react-navigation/native';
useFocusEffect(
React.useCallback(() => {
// YOUR CODE WHEN IT IS FOCUSED
return // YOUR CODE WHEN IT'S UNFOCUSED
}, [userId])
);
Upvotes: 4
Reputation: 1569
The best solution is using NavigationEvents. You don't need to create listeners manually.
Calling a callback function is not highly recommended. Check this example using a listener (Remember to remove all listeners from componentWillUnMount with this option).
Component A:
navigateToComponentB() {
const { navigation } = this.props
this.navigationListener = navigation.addListener('willFocus', payload => {
this.removeNavigationListener()
const { state } = payload
const { params } = state
//update state with the new params
const { otherParam } = params
this.setState({ otherParam })
})
navigation.push('ComponentB', {
returnToRoute: navigation.state,
otherParam: this.state.otherParam
})
}
removeNavigationListener() {
if (this.navigationListener) {
this.navigationListener.remove()
this.navigationListener = null
}
}
componentWillUnmount() {
this.removeNavigationListener()
}
Commponent B:
returnToComponentA() {
const { navigation } = this.props
const { routeName, key } = navigation.getParam('returnToRoute')
navigation.navigate({ routeName, key, params: { otherParam: 123 } })
}
For more details of the previous example: https://github.com/react-navigation/react-navigation/issues/288#issuecomment-378412411
Upvotes: 11
Reputation: 4634
With React Navigation v5, just use the navigate method. From the docs:
To achieve this, you can use the navigate method, which acts like goBack if the screen already exists. You can pass the params with navigate to pass the data back
Full example:
import React from 'react';
import { StyleSheet, Button, Text, View } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
const Stack = createStackNavigator();
function ScreenA ({ navigation, route }) {
const { params } = route;
return (
<View style={styles.container}>
<Text>Params: {JSON.stringify(params)}</Text>
<Button title='Go to B' onPress={() => navigation.navigate('B')} />
</View>
);
}
function ScreenB ({ navigation }) {
return (
<View style={styles.container}>
<Button title='Go to A'
onPress={() => {
navigation.navigate('A', { data: 'Something' })
}}
/>
</View>
);
}
export default function App() {
return (
<NavigationContainer>
<Stack.Navigator mode="modal">
<Stack.Screen name="A" component={ScreenA} />
<Stack.Screen name="B" component={ScreenB} />
</Stack.Navigator>
</NavigationContainer>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
});
Upvotes: 12
Reputation: 11048
You can pass a callback function as parameter when you call navigate like this:
const DEMO_TOKEN = await AsyncStorage.getItem('id_token');
if (DEMO_TOKEN === null) {
this.props.navigation.navigate('Login', {
onGoBack: () => this.refresh(),
});
return -3;
} else {
this.doSomething();
}
And define your callback function:
refresh() {
this.doSomething();
}
Then in the login/registration view, before goBack, you can do this:
await AsyncStorage.setItem('id_token', myId);
this.props.navigation.state.params.onGoBack();
this.props.navigation.goBack();
Update for React Navigation v5:
await AsyncStorage.setItem('id_token', myId);
this.props.route.params.onGoBack();
this.props.navigation.goBack();
Upvotes: 136
Reputation: 5051
is there a way to pass param from
navigate.goback()
and parent can listen to the params and update its state?
You can pass a callback function as parameter (as mentioned in other answers).
Here is a more clear example, when you navigate from A to B and you want B to communicate information back to A you can pass a callback (here onSelect
):
ViewA.js
import React from "react";
import { Button, Text, View } from "react-native";
class ViewA extends React.Component {
state = { selected: false };
onSelect = data => {
this.setState(data);
};
onPress = () => {
this.props.navigate("ViewB", { onSelect: this.onSelect });
};
render() {
return (
<View>
<Text>{this.state.selected ? "Selected" : "Not Selected"}</Text>
<Button title="Next" onPress={this.onPress} />
</View>
);
}
}
ViewB.js
import React from "react";
import { Button } from "react-native";
class ViewB extends React.Component {
goBack() {
const { navigation } = this.props;
navigation.goBack();
navigation.state.params.onSelect({ selected: true });
}
render() {
return <Button title="back" onPress={this.goBack} />;
}
}
Hats off for debrice - Refer to https://github.com/react-navigation/react-navigation/issues/288#issuecomment-315684617
For React Navigation v5
ViewB.js
import React from "react";
import { Button } from "react-native";
class ViewB extends React.Component {
goBack() {
const { navigation, route } = this.props;
navigation.goBack();
route.params.onSelect({ selected: true });
}
render() {
return <Button title="back" onPress={this.goBack} />;
}
}
Upvotes: 73
Reputation: 21
I would also use navigation.navigate. If someone has the same problem and also uses nested navigators, this is how it would work:
onPress={() =>
navigation.navigate('MyStackScreen', {
// Passing params to NESTED navigator screen:
screen: 'goToScreenA',
params: { Data: data.item },
})
}
Upvotes: 0
Reputation: 11
First screen
updateData=(data)=>{
console.log('Selected data',data)
}
this.props.navigation.navigate('FirstScreen',{updateData:this.updateData.bind(this)})
Second screen
// use this method to call FirstScreen method
execBack(param) {
this.props.navigation.state.params.updateData(param);
this.props.navigation.goBack();
}
Upvotes: -1
Reputation: 5269
For those who don't want to manage via props, try this. It will call everytime when this page appear.
Note* (this is not only for goBack but it will call every-time you enter this page.)
import { NavigationEvents } from 'react-navigation';
render() {
return (
<View style={{ flex: 1 }}>
<NavigationEvents
onWillFocus={() => {
// Do your things here
}}
/>
</View>
);
}
Upvotes: 23
Reputation: 3227
I was facing a similar issue, so here is how I solved it by going more into details.
Option one is to navigate back to parent with parameters, just define a callback function in it like this in parent component:
updateData = data => {
console.log(data);
alert("come back status: " + data);
// some other stuff
};
and navigate to the child:
onPress = () => {
this.props.navigation.navigate("ParentScreen", {
name: "from parent",
updateData: this.updateData
});
};
Now in the child it can be called:
this.props.navigation.state.params.updateData(status);
this.props.navigation.goBack();
Option two. In order to get data from any component, as the other answer explained, AsyncStorage can be used either synchronously or not.
Once data is saved it can be used anywhere.
// to get
AsyncStorage.getItem("@item")
.then(item => {
item = JSON.parse(item);
this.setState({ mystate: item });
})
.done();
// to set
AsyncStorage.setItem("@item", JSON.stringify(someData));
or either use an async function to make it self-update when it gets new value doing like so.
this.state = { item: this.dataUpdate() };
async function dataUpdate() {
try {
let item = await AsyncStorage.getItem("@item");
return item;
} catch (e) {
console.log(e.message);
}
}
See the AsyncStorage docs for more details.
Upvotes: 20
Reputation: 1531
I just used standard navigate function giving ViewA route name and passing the parameters, did exactly what goBack would have done.
this.props.navigation.navigate("ViewA",
{
param1: value1,
param2: value2
});
Upvotes: 3