non-serializable values were found in the navigation state when passing a function in params

Viewed 28512

I have two screens:

Screen A

import React, { useState } from "react";
import { Text, View, Button } from "react-native";
const ViewA = ({ navigation }) => {
  const [val, setVal] = useState(null);
  const [val2, setVal2] = useState(null);
  const callBack = (value1,value2) => {
    setVal(value1);
    setVal2(value2);
  };
  const onNextPress = () => {
    navigation.navigate("Second Screen", { callBack: callBack });
  };
  return (
    <View>
      <Text>{val}{val2}</Text>
      <Button title="Next" onPress={onNextPress} />
    </View>
  );
};
export default ViewA;

Screen B

import React from "react";
import { View, Button } from "react-native";

const ViewB = ({ route, navigation }) => {
  const onBackPress = () => {
    const { callBack } = route.params;
    callBack(5,6); // Your new value to set
    navigation.goBack();
  };

  return (
    <View>
      <Button title="back" onPress={onBackPress} />
    </View>
  );
};
export default ViewB;

when I enter screen B a warning appears: non-serializable values were found in the navigation state. how can I solve it?

2 Answers

According to the docs for I get the warning "Non-serializable values were found in the navigation state"

This can happen if you are passing non-serializable values such as class instances, functions etc. in params. React Navigation warns you in this case because this can break other functionality such state persistence, deep linking etc.

...

If you don't use state persistence or deep link to the screen which accepts functions in params, then the warning doesn't affect you and you can safely ignore it. To ignore the warning, you can use YellowBox.ignoreWarnings.

import { YellowBox } from 'react-native';

YellowBox.ignoreWarnings([
  'Non-serializable values were found in the navigation state',
]);

An alternative would be to move the state into route params instead of local state of ViewA (which means you can set it when navigating):

import React, {useState, useEffect} from 'react';
import { Text, View, Button } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';

const Stack = createStackNavigator();

const ViewA = ({ route, navigation }) => {
  const onNextPress = () => {
    navigation.navigate("ViewB", {
      previousScreen: route.name
    });
  };

  return (
    <View>
      <Text>ViewA</Text>
      <Text>Params: {JSON.stringify(route.params)}</Text>
      <Button title="Next" onPress={onNextPress} />
    </View>
  );
};

const ViewB = ({ route, navigation }) => {
  const onBackPress = () => {
    navigation.navigate(route.params.previousScreen, {
      val: 5,
      val2: 6,
    })
  };

  return (
    <View>
      <Text>ViewB</Text>
      <Text>Params: {JSON.stringify(route.params)}</Text>
      <Button title="back" onPress={onBackPress} />
    </View>
  );
};

export default function App() {
  return (
    <NavigationContainer>
      <Stack.Navigator mode="modal">
        <Stack.Screen name="ViewA" component={ViewA} />
        <Stack.Screen name="ViewB" component={ViewB} />
      </Stack.Navigator>
    </NavigationContainer>
  );
}

Snack

anyway, if you still need to run that callback from another screen, you can make a custom class to subscribe to events and store the callback like so:

class SetNewPropsListener {
    constructor(){
        this.listeners = []

        this.subscribe = ({ id, cb }) => {
            this.listeners = [...this.listeners.filter((x)=> x.id !== id), { id, cb }]
        }

        this.unsubscribe = ({ id }) => {
            this.listeners = this.listeners.filter((x)=> x.id !== id)
        }

        this.propogate = (id, newProps) => {
            this.listeners.forEach((x)=> x.id === id && x.cb(newProps))
        }
    }
}

export const SetNewProps = new SetNewPropsListener()

and then, in the first screen you're navigating from, you can register callback function with unique id like so:

import { SetNewProps } from '../utils/EventListeners'
const callbackToInvokeFromOtherScreen = (newParamsFromNextScreen)=>{
    // do whatever with new values
}
componentDidMount(){
  SetNewProps.subscribe({ id: 'your_unique_id', cb: callbackToInvokeFromOtherScreen })
}

and then in the next screen you navigate to, you can access the stored callback from the SetNewProps class instance and execute it with your custom params like so:

import { SetNewProps } from '../utils/EventListeners'
const propsToPassToCallback = {}
SetNewProps.propogate('your_unique_id', propsToPassToCallback)

with right logic, this subscriber class method can solve many problems, like invoking peer components methods (like when you have swipe to delete interaction, you don't want any other ListView item to be left open when you swiped another one)

Related