Search code examples
reactjsreact-nativereact-reduxreact-contextreact-state-management

What is the correct way to pass data between two different screens in React Native?


The following examples are extremely simplified but still enough to sum up my question. Currently I'm passing data (an object) between two screens using React Navigation parameters and it works just fine:

export const First screen = ({ navigation }) => {
  const object = {
    name:"John"
    surname:"Smith"
  };

  return (

      <Button
        onPress={() =>
          navigation.navigate("MainStack", {
                  screen: "SecondScreen",
                  params: { object: object },
                });
        }
      />
    </View>
  );
};
export const Second screen = ({ route }) => {
  const [object, setObject] = useState({});

  useEffect(() => {
    if (route.params?.object) setObject(route.params?.object);
  }, []);

  return (
    <View>
      <Text> {object.name} </Text>
      <Text> {object.surname} </Text>
    </View>
  );
};


However, as the docs say, this is considered to be an anti pattern, which makes sense because we are unnecessarily duplicating data:

It's important to understand what kind of data should be in params. Params are like options for a screen. They should only contain information to configure what's displayed in the screen. Avoid passing the full data which will be displayed on the screen itself (e.g. pass a user id instead of user object). Also avoid passing data which is used by multiple screens, such data should be in a global store.

We obviously need to use some global state and then use navigation parameters to help us select the exact data that we need from it. While Redux is probably an overkill, just using React Context is much more suitable for this situation. However, is there an even better way of doing it, a way that is considered to be best practice?


Solution

  • To share data use the built in React Context and Provider. I'm pulling examples from a side project so forgive the names.

    Start by building a Context. This example is for a Queue:

    // QueueContext.js
    import { createContext, useContext } from "react";
    
    const QueueContext = createContext({});
    
    export function useQueueContext() {
        return useContext(QueueContext)
    }
    
    export default QueueContext;
    

    You can pass data, functions, etc as your state. Here I'm using a reducer as my state.

    // index.js
    const [state, dispatch] = useReducer(QueueReducer, initialState);
    const queueState = { state, dispatch };
    
    <QueueContext.Provider value={queueState}>
        <App />
    ...
    

    Now I can use this state in any component that is a child of the provider.

    // my component
    import { useQueueContext } from "../../contexts/QueueContext";
    import Item from './Item';
    
    export default function Queue() {
        const { state, _ } = useQueueContext()
    
        return (
            <>
                <div>Queue {state?.count}</div>
                <ul>
                    {state?.items.map((item) => <Item key={item.id} props={item} />)}
                </ul>
            </>
        )
    }