How to Pass Data Back When Using goBack in React Native Navigation (without callback)

Steve Blue
3 min readJun 20, 2024

--

Passing data back between screens in React Native Navigation can be a bit tricky, especially if you want to avoid warnings and potential issues. Here’s a simple guide to help you handle this effectively, compatible with all versions of React Native Navigation.

The Problem with Callback Functions

One common method is to pass a callback function to the previous screen. However, this can lead to a warning:

“Non-serializable values were found in the navigation state. 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.”

Although this approach works as expected, it’s not ideal due to the warning. You can read more about this issue and how to fix it here.

Now, let’s dive into my solution.

1 . Extend the type of the AppStackScreenProps to include onBackPayload, even when invoking navigation.goBack(), and use the useResult event as a hook in the screen.

type ExtendScreenProps = {
onBackPayload: (payload: Record<string, any> ) => void
useResult: (onResult: (payload: Record<string, any>) => void) => void
}

export type AppStackScreenProps<T extends keyof AppStackParamList> = NativeStackScreenProps<
AppStackParamList,
T
> & ExtendScreenProps

2. Create a hook that uses useRef for getting and setting payloads:

export const useNavigationPayload = () => {
const backPayload = useRef<Record<string, any>>({})
const setBackPayload = useCallback((payload, screen) => {
backPayload.current[BACK_FROM] = screen
backPayload.current[screen] = payload
}, []) as (payload: Record<string, any>, screen: string) => void
return {backPayload, setBackPayload}
}

3. Create a higher-order component withNavigationPayload. This HOC handles the useResult hooks, which can be used in the screen to get the payload when navigating back.

export const withNavigationPayload = (Screen: any, payloadRef: React.MutableRefObject<Record<string, any>>, onBackPayload: (payload: Record<string, any>, screen: string) => void = () => undefined) => {
const useResult = (onResult: (payload: Record<string, any>) => void) => {
useFocusEffect(
useCallback(() => {
onResult(payloadRef.current)
if(payloadRef.current[BACK_FROM]) {
delete payloadRef.current[payloadRef.current[BACK_FROM]]
}
return () => undefined;
}, [])
);
}
const handleOnBackPayload = (payload: Record<string, any>) => {
onBackPayload(payload, Screen.name)
}
return (props: any) => {
return (
<Screen
{...props}
onBackPayload={handleOnBackPayload}
useResult={useResult}
/>
)
};
}

4. Now we set up the HOC withNavigationPayload and the hook useNavigationPayload in the Stack Navigator.

const AppStack = () => {
const { backPayload, setBackPayload } = useNavigationPayload()
return (
<Stack.Navigator>
<Stack.Screen
name="Main"
component={withNavigationPayload(Screens.MainScreen, backPayload)}
/>
<Stack.Screen
name="Preview"
component={withNavigationPayload(Screens.Preview, backPayload, setBackPayload)}
/>
</Stack.Navigator>
)
}

5. Simply pass the useResult prop to your MainScreen component, and it will automatically handle the payload when navigating back.

interface MainScreenProps extends AppStackScreenProps<"Main"> {}

export const MainScreen: FC<MainScreenProps> = ({ navigation, useResult }) => {

const handlePreviewOnPress = () => {
navigation.navigate("Preview")
}

useResult(payload => {
const backFrom = payload[BACK_FROM]
const data = payload[backFrom]
// TODO handle setData(data)
})

return (
<View style={$container}>
<SafeAreaView style={$container}>
<Button onPress={handlePreviewOnPress} />
</SafeAreaView>
</View>
)
}

6. Utilize the onBackPayload prop and navigate back to pass the data back.


interface PreviewScreenProps extends AppStackScreenProps<"Preview"> {}

export const Preview = (props: PreviewScreenProps) => {
const [value, setValue] = useState<string>()
return (
<View style={$root}>
<SafeAreaView style={$root}>
<TextField placeholder="set value" value={value} onChangeText={setValue}/>
<Button
onPress={() => {
props.navigation.goBack()
props.onBackPayload({"payload": value})
}}
>
Back with Payload
</Button>
<Button onPress={()=> props.navigation.goBack()}>Without Payload</Button>
</SafeAreaView>
</View>
)
}

const $root: ViewStyle = {
flex: 1,
}
Demo

Well done! Navigating data between screens in React Native Navigation can be achieved effectively through the utilization of custom hooks, higher-order components, and extended screen props. By the useNavigationPayload hook to manage payload data and the withNavigationPayload higher-order component to facilitate payload retrieval streamline the process of passing data between screens.

Leveraging the useResult hook enables seamless reception of payload data upon navigating back.

This approach not only simplifies the development process but also enhances code readability and maintainability. With these techniques at hand, developers can efficiently handle data flow within their React Native applications, providing a smoother user experience.

--

--

Steve Blue

Experienced Mobile Application Developer with a demonstrated history of working in the computer software industry.