Sennen Randika
Sennen Randika

Reputation: 1646

How to listen to navigation state params changes in React Native?

I'm developing a Mobile Application using React Native. There, I'm passing something when navigating to the second page.

So, assume my first screen has something like this.

export default function firstScreen(props) {
  return(
    //data is a custom object variable that may be changed.
    <Button onPress={() => props.navigation.navigate('SecondScreen', { data: data } )} /> 
  )
}

On my second screen, assume there is something like this...

export default function secondScreen(props) {
  const { data } = props.navigation.state.params;

  useEffect(() => {
    console.log(data);
  }, [data])


  return(
    //content
  )
}

My problem is, when data is changed from the first screen, the second screen does not listen to that change and print the content of the data variable on the console.

So, I want to know, how to listen to the navigation state changes in React Native.

Upvotes: 6

Views: 15123

Answers (3)

Dallin Romney
Dallin Romney

Reputation: 2761

You might also look into using an event listener on a navigation ref that is attached to your navigation container. See docs here: https://reactnavigation.org/docs/navigation-events/

The navigationRef can be exported and accessed most anywhere. If you wanted to check navigation state from a parent of the navigation container, it could look something like this:

import { useEffect } from 'react'
import {
    createNavigationContainerRef,
    EventListenerCallback,
    NavigationContainerEventMap,
    NavigationContainer,
} from '@react-navigation/native'
import { createNativeStackNavigator } from '@react-navigation/native-stack'

type StackParamList = {
    Screen1: undefined
    Screen2: undefined
}
const Screen1 = () => <></>
const Screen2 = () => <></>

const navigationRef = createNavigationContainerRef<StackParamList>()
const Stack = createNativeStackNavigator<StackParamList>()

const Navigation = () => (
    <NavigationContainer ref={navigationRef}>
        <Stack.Navigator>
            <Stack.Screen name="Screen1" component={Screen1} />
            <Stack.Screen name="Screen2" component={Screen2} />
        </Stack.Navigator>
    </NavigationContainer>
)

const App = () => {
    useEffect(() => {
        const handleStateChange: EventListenerCallback<
            NavigationContainerEventMap,
            'state'
        > = (event) => {
            const state = event.data.state

            // do stuff with state
            console.log(state)
        }

        // Add listener on mount
        navigationRef.addListener('state', handleStateChange)

        // Remove listener on unmount
        return () => navigationRef.removeListener('state', handleStateChange)
    }, [navigationRef])

    return <Navigation />
}

export default App

Listening for this event is even easier within the navigation container; you can use the useNavigationState hook inside components per Guruparan's answer if you want access to component state, or you can use the onStateChange callback built into navigation containers:

    <NavigationContainer
        onStateChange={(state) => {
            // do stuff with state
            console.log(state)
        }}
    >
        <Stack.Navigator>
            <Stack.Screen name="Screen1" component={Screen1} />
            <Stack.Screen name="Screen2" component={Screen2} />
        </Stack.Navigator>
    </NavigationContainer>

Upvotes: 2

hasn
hasn

Reputation: 807

Following is how I solved this problem with hooks in React Navigation V5:

import { useRoute } from '@react-navigation/native';
const route = useRoute();
useEffect(() => { //This will run whenever params change
     const {params = {}} = route;
    //your logic here
}, [route]);

Upvotes: 6

Guruparan Giritharan
Guruparan Giritharan

Reputation: 16354

You can use the hook 'useNavigationState' to get the navigation state from the second screen.

Then again you will have the problem of actually updating it from the first screen. When you pass a parameter its part of the navigation state and the changes you to do the object you have wont be reflected there.

One way to update navigation state would be to dispatch an action, but that will be a problem because you need to find the key of the screen you are going to update. the dispatch would be something like this

navigation.dispatch({ ...CommonActions.setParams({ data: {} }), source: route.key, });

As you have the need to get the updates of your object to another screen you should consider something like react context or if you already have redux in your app you can utilize that if the need is there.

Easiest way would be the react context which you can update from one screen and receive from another.

The version of using context would be like below. Here a simple stack navigator is used and the context is updated from home screen and the value can be read from settings screen. You can use the same way to update the data variable and get the data from the other screen. Here we dont use the navigation state but we maintain our own context for the variable.

const Stack = createStackNavigator();
const CountContext = React.createContext({ count: 1, setCount: () => {} });

function HomeScreen({ navigation, route }) {
  const { count, setCount } = React.useContext(CountContext);
  React.useEffect(() => {
    const interval = setInterval(() => {
      setCount(count => count + 1);
    }, 1000);

    return () => {
      clearInterval(interval);
    };
  }, []);

  return (
    <View>
      <Text>Test{count}</Text>
      <Button
        title="Go to settings"
        onPress={() => navigation.navigate('Settings')}
      />
    </View>
  );
}

function SettingsScreen({ navigation, route }) {
  const { count, setCount } = React.useContext(CountContext);
  return (
    <View>
      <Text>Settings! {count}</Text>
      <Button title="Go back" onPress={() => navigation.goBack()} />
    </View>
  );
}

export default function App() {
  const [count, setCount] = React.useState(0);
  const value = { count, setCount };

  return (
    <CountContext.Provider value={value}>
      <NavigationContainer>
        <Stack.Navigator>
          <Stack.Screen name="Home" component={HomeScreen} />
          <Stack.Screen name="Settings" component={SettingsScreen} />
        </Stack.Navigator>
      </NavigationContainer>
    </CountContext.Provider>
  );
}

You can check out this snack https://snack.expo.io/@guruparan/stateparamschange

Upvotes: 2

Related Questions