How to Pass Data Back When Using goBack in React Native Navigation (without callback)
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,
}
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.