[react-native] React navigation goBack() and update parent state

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

enter image description here

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

enter image description here

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?

This question is related to react-native react-navigation

The answer is


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();

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

EDITED: 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

Regards, Nicholls


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',
  },
});

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

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


Edit

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} />;
  }
}

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

If you are using redus 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


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

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.