Home > Net >  react native context state becomes undefined when navigated to a screen
react native context state becomes undefined when navigated to a screen

Time:12-14

I'm using react-navigation v6 and context api, when I navigate from my component to a screen, the context state image which even though wraps my component, becomes undefined. Below is the code flow and relevant code from my components & screens.

EXPECTED OUTPUT -> image context state should persist (as I'm using context provider)
ACTUAL OUTPUT -> image context state becomes undefined when I navigate to different screen.

AppStack

const AppStack = () => {
  return (
    <Stack.Navigator>
      <Stack.Screen name={'AppDrawer'} component={AppDrawer} />
      <Stack.Screen name={'StoreStack'} component={StoreStack} />
    </Stack.Navigator>

  )
}

AppDrawer

const AppDrawer = () => {
  return (
    <Drawer.Navigator>
      <Drawer.Screen name={'AppBotTab'} component={AppBotTab} />
    </Drawer.Navigator>
  )
}

AppBotTab

const BotTab = createBottomTabNavigator();
const AppBotTab = () => {
  return (
    <BotTab.Navigator>
      <BotTab.Screen name='CameraPreview' component={CameraPreview} />
      //... other screens
    </BotTab.Navigator>
  )
}

CameraPreview -> The Culprit (seems like)

const CameraPreview = ({ navigation }) => {
  const isFocused = useIsFocused();
  const cameraRef = React.useRef<ExpoCamera>(null);
  const [isCameraReady, setIsCameraReady] = useState(false);
  const [cameraType, setCameraType] = useState(CameraType.back);

  return (
        isFocused && (
            <Camera
                cameraRef={cameraRef}
                cameraType={cameraType}
                flashMode={flashMode}
                onCameraReady={() => setIsCameraReady(true)}>
                  <MediaContextProvider>
                    <CameraControls
                      isCameraReady={isCameraReady}
                      onToggleCamera={toggleCamera}
                      setFlashMode={setFlashMode}
                      cameraRef={cameraRef}
                      onDismiss={() => navigation?.goBack()}
                    />
                  </MediaContextProvider>
            </Camera>
        )
    );
}

CameraControls

const CameraControls = () => {
  const { image, setImageAndUpdateSize } = useMedia();
  const [cameraState, setCameraState] = useState('PREVIEW');
  
  useEffect(() => {
        console.log('Mounting CAMERA CONTROLS');
        return () => {
            console.log('image: ', image); // undefined
            console.log('Umnounting CAMERA CONTROLS'); // gets called when navigating to StoreStack screen
        };
  }, []);

  const takePhoto = async () => {
        if (cameraRef.current) {
            const data = await cameraRef.current.takePictureAsync(CAMERA_OPTIONS);
            const source = data.uri;
            if (source) {
                setImageAndUpdateSize({ path: source })
                cameraRef.current.pausePreview();
                setCameraState('IMAGE_PREVIEW');
            }
        }
  };

  switch (cameraState) {
        case 'PREVIEW':
            return <CameraPreviewControls onTakePhoto={takePhoto} />;
        case 'IMAGE_PREVIEW':
            if (!image?.path) return null;
            return <PhotoPreview imageURI={image?.path} />;
        default:
            return null;
    }
}

PhotoPreview


const PhotoPreview = ({ imageURI }) => {
  useEffect(() => {
        console.log('Mounting PHOTO REVIEW');
        return () => {
            console.log('Umnounting PHOTO PREVIEW'); // gets called when navigating to StoreStack screen
        };
  }, []);

  return (
    <ImageBackground source={{uri:imageURI}} />
    //... other components
    <FiltersPanel />
  )
}

FiltersPanel

const FiltersPanel = () => {
  ...
  return (
    //...
    <Gifts />
  )
}

Gifts Component

const Gifts = () => {
  const navigation = useNavigation();
  const goToStore = () => {
      navigation.navigate('StoreStack', {
          screen: StoreStackPages.StoreScreen,
      });
  };

    return (
        <TouchableOpacity onPress={goToStore}>
            <Image source={GIFT} resizeMode="contain" style={styles.image} />
        </TouchableOpacity>
    );
}

MediaContext

const MediaContext = createContext({
  // other default values
  setImage: () => {},
});

const MediaContextProvider = ({ children }) => {
  const [image, setImage] = useState<Partial<PickerImage>>();
  // other states
  return (
    <MediaContextProvider value={{image, setImage}}></MediaContextProvider>
  )
}

export const useMedia = () => {
  const context = useContext(MediaContext);
  const setImageAndUpdateSize = (capturedImage) => {
    const {setImage} = context;
    return setImage(capturedImage);
  }

  return {
    ...context,
    // other functions & utilities
    setImageAndUpdateSize
  }
}

This is the whole flow. So, again, the problem is, in simple words, when I capture a photo in CameraPreviewControls, the image is set successfully and as the cameraState now changes to 'IMAGE_PREVIEW', the PhotoPreview component gets mounted, and I can see the ImageBackground loaded with my context image, now when I tap the GIFT icon (being in the PhotoPreview component), I navigate to StoreStack, BUT NOW when I go back from StoreStack, the context image changes to undefined and the cameraState changes to PREVIEW which shouldn't be the case as I'm still wrapped under MediaContextProvider so the image context state shouldn't be undefined and also when I navigate to StoreStack and comes, the CameraControls and PhotoPreview component gets un-mounted which also shouldn't be the case in react navigation, as according to react-navigation docs, when we navigate to a screen, the previous screen does not get un-mounted.

Can someone shed some light on this, it would be really helpful!!

Thank you in advance!

CodePudding user response:

useIsFocused is triggering a rerender when you navigate away. When you come back the context is rerendered. useIsFocused can be removed to stop the rerender when navigating away.

  • Related