101

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?

Ioana B
  • 195
  • 3
  • 16
Darren Lau
  • 1,995
  • 4
  • 25
  • 40

18 Answers18

136

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();
Zhang Buzz
  • 10,420
  • 6
  • 38
  • 47
  • 1
    Hi, if I use `static navigationOptions = ({navigation}) => ({ headerRight: – Khalil Khalaf Jul 11 '17 at 19:44
  • Even if i don't use `this.props.navigation.state.params.onGoBack();`, it is still calling `refresh()` – Ravi Sep 25 '17 at 11:21
  • I can't find the reference now, but I saw a warning from a react-navigation developer that callback functions in params aren't guaranteed to work, because the params can be serialized into a file and then reinstated, at which point your callbacks will be lost. – Jules Apr 14 '18 at 10:46
  • 3
    You can also use `onGoBack: this.refresh` instead of `onGoBack: () => this.refresh()` – dǝɥɔS ʇoıןןƎ Sep 20 '18 at 15:20
  • This pretty much requires a gist or a little bit more defining of what is actually happening in explicit code, because the concept communicated is large and also important. – mibbit Jan 11 '19 at 20:20
  • Still not solve my issue :( im using react navigation 2.0 – Mike Victoria Jan 02 '20 at 02:19
  • 2
    For React Navigation 5.0, use this.props.route.params.onGoBack() – The1993 Mar 06 '20 at 07:59
  • 51
    In v5, you will get "We found non-serializable values in the navigation state, which can break usage such as persisting and restoring state if you just pass a callback function. – Toma Radu-Petrescu Apr 12 '20 at 08:53
  • Any ideas how to solve the warning "We found non-serializable values in the navigation state, which can break usage such as persisting and restoring state if you just pass a callback function" in v5? – Victor Molina Aug 02 '20 at 21:51
  • One option is to use navigation events in the parent. The event will fire each time the view is navigated to. This isn't quite the same but in some cases it's close enough. https://reactnavigation.org/docs/navigation-events/ – mrvladimir Sep 09 '20 at 07:44
  • 16
    The official option is to ".navigate" back to the view instead of ".goBack", and pass in a parameter. Since the route name is the same, it will pop the current route off instead of adding a new one. If the parent is a function component you can do ```useEffect(refresh, [route.params?.updateTime])``` and in the child do ```.navigate("parent", {updateTime: new Date().getTime()})```. – mrvladimir Sep 09 '20 at 07:51
  • Use this answer https://stackoverflow.com/a/64257347/5501242 – nahoang Oct 08 '20 at 07:04
  • Navigation events used to return which screen you came from, which was useful. On v5 they don't anymore, so you're stuck with sending the target screen where you're coming from, and `navigate`ing from there instead of `goBack()`ing – JaviCasa Dec 23 '20 at 04:56
  • But if I write an common page that do something with a callback, so I don't know `parent page`'s name. What can do in this situation? – user3875388 Nov 15 '21 at 03:10
72

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} />;
  }
}
iku
  • 133
  • 2
  • 9
gihanchanuka
  • 4,783
  • 2
  • 32
  • 32
  • 1
    This was very helpful for a case I had today when I needed to keep track of unsaved items on a page. Worked perfectly. – IronWorkshop Feb 11 '19 at 07:13
  • 2
    Hey! I might be late, but Im getting an error called ```undefined is not an object (evaluating navigation.state.params.onSelect)``` is not a function, – Min UD Jan 23 '20 at 18:39
  • Thanks for sharing the solution from Debrice. It works perfectly as expected (: – Tommy Leong Aug 14 '20 at 06:05
  • @MinUD please use `this.props.route.params.onSelect` – Ali Akram Aug 17 '20 at 21:34
  • I had to add `bind(this)` in order `this.setState()` be able to work. So `onPress = () => { this.props.navigate("ViewB", { onSelect: this.onSelect.bind(this) }); };` – FosAvance Dec 05 '20 at 16:22
  • @FosAvance did you try this? `{ onSelect: () => this.onSelect() }` – gihanchanuka Dec 06 '20 at 11:28
  • @gihanchanuka it's not working, nothing is passed back then – FosAvance Dec 06 '20 at 12:16
  • 1
    P.S. because if `bind(this)` I get: "Non-serializable values were found in the navigation state. Check: Contacts > params.returnData (Function) This can break usage such as persisting and restoring state. This might happen if you passed non-serializable values such as function, class instances etc. in params. " – FosAvance Dec 06 '20 at 12:34
23

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>
    );
}
Mahdi Bashirpour
  • 17,147
  • 12
  • 117
  • 144
Sagar Chavada
  • 5,169
  • 7
  • 40
  • 67
  • 1
    weird, but best solution ever. it helps when we wants to refresh anytime. – Sunil Zalavadiya Feb 06 '19 at 09:18
  • 1
    This is the best answer, simple and very effective, I needed to refresh a screen every time I went back to it, with either a navigation or a back press, and this solves it with no complications. – Danilo Mar 21 '19 at 17:13
20

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.

Gianfranco P.
  • 10,049
  • 6
  • 51
  • 68
Nerius Jok
  • 3,059
  • 6
  • 21
  • 27
  • First approach worked for me. My objective was to refersh some data when child is poped. – Waseem Sarwar Jul 01 '18 at 11:57
  • @David In my case i gave headerMode as screen.So that the back button at left is automatically provided.I dont want to change the icon.But i want to do some function after goBack() to previous screen.Please tell me how to do this? – Johncy Sep 05 '18 at 11:04
  • 1
    see also this example with the same approach as Option 1 https://github.com/react-navigation/react-navigation/issues/1416#issuecomment-300489310 – Gianfranco P. Sep 19 '18 at 19:49
  • Option 1 like closure in swift. it's work exactly, thanks bro – Chris Nguyen Mar 06 '19 at 15:55
  • I got a warning Message dude " [Unhandled promise rejection: TypeError: undefined is not an object (evaluating '_this.props.navigation.state.params')] " – Jansha Mohammed Jun 21 '20 at 14:20
11

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

karel
  • 5,489
  • 46
  • 45
  • 50
jdnichollsc
  • 1,520
  • 1
  • 24
  • 44
8

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',
  },
});
Pedro Andrade
  • 4,556
  • 1
  • 25
  • 24
  • 1
    This is the correct (up-to-date) answer - wonder why it is so far down. – MauriceNino May 10 '22 at 11:06
  • 1
    this messes up the stack history. goBack removes the the current screen from the history stack while `navigation.navigate` just pushes new screen on the stack. – Dawit Oct 16 '22 at 05:30
7

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;
cotex
  • 71
  • 1
  • 4
4

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])
);
Jeremy Caney
  • 7,102
  • 69
  • 48
  • 77
  • While some of the other solutions could also work, for me this was the best solution 'cause it meant that I didn't need to muddy the state going between components just to get a component to re-render when the screen gains focus again. – jnotelddim Feb 10 '22 at 16:53
3

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
});
David Schumann
  • 13,380
  • 9
  • 75
  • 96
morgan_il
  • 1,501
  • 16
  • 19
  • 8
    This is **NOT** the same as `goBack()`. This goes forwards to the previous view (it's adding to the stack). And furthermore, I don't think this answers the question. – dǝɥɔS ʇoıןןƎ Sep 20 '18 at 15:23
  • I think it does - the question is "how to go back with params". That is regardless of your comment on going back vs going forward . In either case the params can be passed and received by the "back" screen. – morgan_il Sep 20 '18 at 19:12
  • But... you’re not “going back” with this method. You’re only going forwards to the previous page, and passing the same props as was originally used on it (plus any new ones). This can be visualised with the transitions used. For example, going forwards might slide a new page in from the right (with a shadow so it appears above the current page) and have it finish on the left, while going back might slide the current page from the left to the right, leaving the previous page “underneath” (appearing so with the use of shadow). – dǝɥɔS ʇoıןןƎ Sep 20 '18 at 20:57
  • 3
    @ElliotSchep If the route is already on the stack, calling `this.props.navigation.navigate` *is* the same as calling `goBack()`. Test it out, you'll see. The screen animation slides from left to right. – Bataleon Oct 24 '18 at 11:31
  • 3
    @Bataleon I wasn't aware of this. Sorry morgan_il – dǝɥɔS ʇoıןןƎ Oct 24 '18 at 11:32
  • 2
    @ElliotSchep No problem, it's a bit confusing and not what I would call "expected" behavior. I also assumed `navigation.navigate` always resulted in a route being pushed onto the stack. – Bataleon Oct 24 '18 at 11:36
  • navigation.navigate will go back to that route if its in the stack. If you want to push new navigation to the stack use navigation.push – Miki Pavlov Aug 21 '19 at 10:54
2

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

Ali Akbar Azizi
  • 3,272
  • 3
  • 25
  • 44
1

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.

sayalok
  • 882
  • 3
  • 15
  • 30
Amer NM
  • 159
  • 2
  • 7
0

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 },
        })
      }
Mark0
  • 21
  • 1
  • 3
0

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]);
Sultan Singh Atwal
  • 810
  • 2
  • 8
  • 19
0

use Merge [take a look (react navigation 6.x)1

  • Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Jun 22 '22 at 18:25
0

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();
Mehdi Faraji
  • 2,574
  • 8
  • 28
  • 76
0

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

Irfan wani
  • 4,084
  • 2
  • 19
  • 34
0
navigation.dispatch(state => {
                        const prevRoute = state.routes[state.routes.length - 2];
                        return CommonActions.navigate({
                            name: prevRoute.name,
                            params: {
                            },
                            merge: true,
                        });
                    });
Mladen Skrbic
  • 154
  • 2
  • 12
-1

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();
}
Mahdi Bashirpour
  • 17,147
  • 12
  • 117
  • 144