Search code examples
react-nativereact-navigationreact-navigation-v5

useFocusEffect hook is invoked even screen is in background when update global state on the screen in foreground


I am developing react-native project. This question is mainly about the useFocusEffect hook behaviour in my project.

I have several screens. There is a share data & each screen can update the data. So, I decided to use context to host the data via useState hook. When one screen setMyData(), the up-to-date data is available to all screens.

My data context to host shared data via useState hook:

import React, {useState} from 'react';

const MyDataContext = React.createContext();

export const MyDataProvider = ({children}) => {
   const [myData, setMyData] = useState([]);
   ...
   return (
    <MyDataContext.Provider
      value={{
        myData,
        setMyData,
      }}>
      {children}
    </MyDataContext.Provider>
  );
}

export default MyDataContext;

(My App component is wrapped by the MyDataContext.Provider, I just don't show that part here since it is not the point of my question)

In each of my screen components, I need to process my data once when the screen is on the foreground (visible), and update the global state with updated data. I use useFocusEffect hook. Something like below:

import {useFocusEffect} from '@react-navigation/native';
import MyDataContext from '../context/MyDataContext';

const MyScreenOne = ({navigation})=> {

   const {myData, setMyData} = useContext(MyDataContext);
   
   useFocusEffect(() => {
     console.log('#################  Screen one is FOCUSED! ');
    
     const updatedData = processData([...myData]);
     // set my data
     setMyData(updatedData);
     return () => {
       console.log('Screen one was unfocused');
     };
   }, []);//empty array to force code in hook only run once

   return (<View>
             <MyButton onPress={()=> navigation.navigate("MyScreenTwo")}>
              ...
           </View>
          )
}
...

As you can see I have a button which navigates user to another screen. The other screen has the same structure of having that useFocusEffect hook in which it processes & updates data of global state.

When I run my app, the following happens to me:

  1. At first MyScreenOne is launched. useFocusEffect is invoked, data is processed and updated and set to global state via the setMyData()

  2. Since setMyData() is called in above step, the re-rendering happens, at this time, the useFocusEffect of MyScreenOne is not invoked again, which is good and expected.

  3. Now I press the button which navigates to MyScreenTwo. Same process happens on MyScreenTwo: useFocusEffect is invoked, data is processed and updated and set to global state via the setMyData(). NOTICE: MyScreenOne is now not visible and in background stack/memory.

  4. Since in above step the global state is updated by the second screen, re-rendering happens again to all screens in memory. This time surprisingly the useFocusEffect of MyScreenOne is invoked again, because of that the global state is updated again by the hook in MyScreenOne again, and re-rendering happens again to screens in memory.

  5. As you can imaging , endless re-rendering happens now due to in above step the background screen MyScreenOne's useFocusEffect is invoked surprisingly.

Isn't useFocusEffect supposed to be run only when screen is visible? Why the useFocusEffect hook of background screen is also invoked when re-rendering happens? How to achieve what I need to only run the process data & update data once on each screen when the screen is visible in the meantime the data can be shared by all screens?


Solution

  • useFocusEffect doesn't take a dependency array. You need to pass useCallback as mentioned in the docs:

    useFocusEffect(
     React.useCallback(() => {
       // whatever
     }, [])
    );
    

    https://reactnavigation.org/docs/use-focus-effect/