75

How are you. This is scenario of this issue. Let's say there are 2 screens to make it simple.

  1. enter A screen. useEffect of A screen called.
  2. navigate to B screen from A screen
  3. navigate back to A screen from B. at this time, useEffect is not called.

    function CompanyComponent(props) {
    
       const [roleID, setRoleID] = useState(props.user.SELECTED_ROLE.id)
    
       useEffect(()=>{ 
    
     // this called only once when A screen(this component) loaded,  
     // but when comeback to this screen, it doesn't called
       setRoleID(props.user.SELECTED_ROLE.id)
     }, [props.user])
    }
    

So the updated state of Screen A remain same when comeback to A screen again (Not loading from props)

I am not changing props.user in screen B. But I think const [roleID, setRoleID] = useState(props.user.SELECTED_ROLE.id) this line should be called at least.

I am using redux-persist. I think this is not a problem. For navigation, I use this

// to go first screen A, screen B
function navigate(routeName, params) {
    _navigator.dispatch(
        NavigationActions.navigate({
            routeName,
            params,
        })
    );
}
// when come back to screen A from B
function goBack() {
    _navigator.dispatch(
        NavigationActions.back()
    );
}

Is there any callback I can use when the screen appears? What is wrong with my code?

Thanks

Nomura Nori
  • 4,689
  • 8
  • 47
  • 85
  • Do you change props.user when you go back to screen A? – Ulukbek Abylbekov Feb 12 '20 at 07:16
  • 1
    The `useEffect` isn't called again because `props.user` remains the same in your case...you can use multiple `useEffects` in one component :) – Ramesh Reddy Feb 12 '20 at 07:16
  • How are you navigating between screen? Can you add some code? – Muhammad Zeeshan Feb 12 '20 at 07:18
  • What navigation package are you using? Does the navigation do more of a stack and not unmount "back" "pages"? The `useEffect` hook will only fire on first render and whenever a value in its dependency array update, so I suspect it isn't getting triggered again by not being remounted. – Drew Reese Feb 12 '20 at 07:41

8 Answers8

143

Below solution worked for me:

import React, { useEffect } from "react";
import { useIsFocused } from "@react-navigation/native";

const ExampleScreen = (props) => {
    const isFocused = useIsFocused();

    useEffect(() => {
        console.log("called");
 
        // Call only when screen open or when back on screen 
        if(isFocused){ 
            getInitialData();
        }
    }, [props, isFocused]);

    const getInitialData = async () => {}    

    return (
        ......
        ......
    )
}

I've used react navigation 5+

@react-navigation/native": "5.6.1"

aphoe
  • 2,586
  • 1
  • 27
  • 31
Nitesh Tosniwal
  • 1,796
  • 2
  • 12
  • 17
  • 22
    I've used ```useEffect(() => { if(isFocused){ console.log("called"); getInitialData(); } }, [ isFocused]);``` Seemed to work better – Tim Aug 27 '20 at 12:13
  • 1
    Perfect, very smart solution – amani rose Apr 26 '21 at 18:54
  • 1
    Why not [useFocusEffect](https://reactnavigation.org/docs/use-focus-effect) ? – Samuel De Backer Mar 23 '22 at 10:39
  • 1
    @SamuelDeBacker useFocusEffect: Use to trigger any function or method with screen gets focused or unfocused. useIsFocused: Use to refresh UI components from the return method when the screen gets focused or unfocused. so, second one is more generic. – Nitesh Tosniwal Mar 23 '22 at 14:33
  • Could someone pls explain me why [props, isfocused] instead of [isfocused] ? – oguz Jul 28 '22 at 16:23
  • @oguz I think the author of the answer just adopted the code to the OP. I am using `[currentDate, isFocused]` to fetch data when a) the date has changed and b) the user navigates back from a view to create data. – alexander Jan 01 '23 at 12:03
21

When you navigate from A to B, component A is not destroyed (it stays in the navigation stack). Therefore, when you navigate back the code does not run again.

Perhaps a better way to acheive what you want to to use the navigation lifecycle events (I am assuming you are using react-navigation) I.e. subscribe to the didFocus event and run whatever code you want whenever the component is focussed E.g

const unsubscribe = props.navigation.addListener('didFocus', () => {
    console.log('focussed');
});

Don't forget to unsubscribe when appropriate e.g.

// sometime later perhaps when the component is unmounted call the function returned from addListener. In this case it was called unsubscribe
unsubscribe();
mahi-man
  • 4,326
  • 2
  • 25
  • 36
  • 2
    May I know how to unscribe? – sanjeev shetty May 23 '20 at 09:35
  • 4
    To unsubscribe, you just need to call the function that was returned from `addListener`. In the above example I called it `unsubscribe`. I have updated the answer above too. Hopefully that makes sense. – mahi-man May 24 '20 at 20:27
  • 4
    If you are using react-navigation 5.x give a try to [useFocusEffect](https://reactnavigation.org/docs/use-focus-effect#how-is-usefocuseffect-different-from-adding-a-listener-for-focus-event) – Cris69 Jul 01 '20 at 23:17
  • true for react-navigation 4.4 – Oscar López Jul 07 '22 at 17:44
12

React Navigation 5 provide a useFocusEffect hook, is analogous to useEffect, the only difference is that it only runs if the screen is currently focused. Check the documentation https://reactnavigation.org/docs/use-focus-effect

 useFocusEffect(
    useCallback(() => {
      const unsubscribe = setRoleID(props.user.SELECTED_ROLE.id)
      return () => unsubscribe()
    }, [props.user])
  )
9

The current version of React Navigation provides the useFocusEffect hook. See here.

Daniel NX
  • 171
  • 2
  • 5
6

The above mentioned solution would work definitely but in any case you need to know why it happens here is the reason,

In react native all the screens are stacked meaning they follow the LAST-IN-FIRST-OUT order, so when you are on a SCREEN A and go.Back(), the component(Screen A) would get un-mounted because it was the last screen that was added in the stack but when we naviagte to SCREEN B, it won't get un-mounted and the next SCREEN B would be added in the stack.

So now, when you go.Back() to SCREEN A, the useEffect will not run because it never got un-mounted. React-native keeps the navigation this way to make it look more responsive and real time.

if you want to un-mount the Screen everytime you navigate to some other screen you might want to try navigation.replace than navigation.navigate

Hope this helps you.

Shivam purbia
  • 228
  • 4
  • 13
4
import { useIsFocused } from "@react-navigation/native"; 
 
const focus = useIsFocused();  // useIsFocused as shown          
 
useEffect(() => {   // whenever you are in the current screen, it will be true vice versa
    if(focus == true){ // if condition required here because it will call the function even when you are not focused in the screen as well, because we passed it as a dependencies to useEffect hook
       handleGetProfile();
    }
}, [focus]);

more

abhish
  • 243
  • 4
  • 8
2

Solution with useEffect

import { useNavigation } from '@react-navigation/native';

const Component = (props) => {
  const navigation = useNavigation()
  const isFocused = useMemo(() => navigation.isFocused(), [])
  useEffect(() => {
    if (isFocused) {
      // place your logic
    }
  }, [isFocused])
}
Kirill Novikov
  • 2,576
  • 4
  • 20
  • 33
1

I was struggling with the same issue but because the data I was updating on screen B and displaying on screen A was also being used on other screens it made perfect sense to put this set of data in a redux store slice.

Besides in my case screen A was placed in a tab navigator and had a few sibling screens in other tabs the user could swipe left/right to. This made useFocusEffect problematic since it was firing on each swipe and for other reasons I wanted to avoid that.

Anyway, with redux I could get away with useEffect not firing on going back. Just a hint alongside other solutions suggested here.

Joey
  • 409
  • 1
  • 3
  • 11