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.