As the docs https://reactnavigation.org/docs/en/next/use-focus-effect.html,
"Sometimes we want to run side-effects when a screen is focused. A side effect may involve things like adding an event listener, fetching data, updating document title, etc."
I'm trying to use useFocusEffect to fetch data everytime that the user go to that page.
on my component I have a function which dispatch an action with redux to fetch the data:
const fetchData = ()=>{
dispatch(companyJobsFetch(userDetails.companyId));
};
Actually I'm using useEffect hook to call fetchData(), but I'd like to fetch data everytime that the user go to that page and not only when rendered the first time.
It's not clear from the documentation how to use useFocusEffect and I'm not having success on how to do it.
Any help?
The docs show you how to do it. You need to replace API.subscribe with your own thing:
useFocusEffect(
React.useCallback(() => {
dispatch(companyJobsFetch(userDetails.companyId));
}, [dispatch, companyJobsFetch, userDetails.companyId])
);
For version react navigation 4.x, you can use addEvent listener
useEffect(() => {
if (navigation.isFocused()) {
resetReviews(); // replace with your function
}
}, [navigation.isFocused()]);
OR
useEffect(() => {
const focusListener = navigation.addListener('didFocus', () => {
// The screen is focused
// Call any action
_getBusiness({id: business?.id}); // replace with your function
});
return () => {
// clean up event listener
focusListener.remove();
};
}, []);
For later version 5.x, you can use hooks to achieve this
import { useIsFocused } from '#react-navigation/native';
// ...
function Profile() {
const isFocused = useIsFocused();
return <Text>{isFocused ? 'focused' : 'unfocused'}</Text>;
}
Related
I want to update the previous screen's useState value and refresh the screen on click of goBack(), I know there is a useIsFocused() to do that but using this screen it is refreshing every time i goBack to the screen, suppose there is a ScreenA and ScreenB, So when user is performing any action in ScreeB then I am dispatching a value using redux and on goBack I am updating the useState value and refreshing the screenA, But it not working, I don't know what's the problem, Please help.
ScreenB
const leaveGroup = () => {
hideMenu();
callLikeMindsApi(`URL`, httpMethods.HTTP_PUT)
.then(response => {
if (response && response.data && response.data.success) {
hideMenu();
dispatch(updateHomeScreen(true));
props.navigation.goBack();
}
})
.catch(error => {
Alert.alert('FAILED', error.response.data.error_message);
});
};
And In ScreenA
const {updateValue} = useSelector(state => state.homeReducer);
useEffect(() => {
console.log('updateValue-1234', updateValue);
}); // it is printing true in console if (updateValue) { setOffset(1); setTotalPages(1); setMyChatRooms([]); getUserData(); } }, [isFocused]);
But
setOffset(1);
setTotalPages(1);
setMyChatRooms([]);
values are not updating, if I remove the if(updateValue){} and writing like this
useEffect(() => {
setOffset(1);
setTotalPages(1);
setMyChatRooms([]);
getUserData();
}, [isFocused]);
then code is working as expected, But it is refreshing every time I come back to the screenA and I want to refresh conditionally.
instead of props.navigation.goBack(); do
props.navigation.navigate('ScreenA',{details});
with the prop that you want pass
in screen A get that value in state from route params
ScreenA = (props) => {
const [navigateState] = useState(props.route.params.details|}{});
---while navigating to Screen B---
props.navigation.navigate('ScreenB',{details: navigateState});
...
I can't get full your code and understand the full logic but if you have already implemented redux you should use it like this:
const {updateValue} = useSelector(state => state.homeReducer);
useEffect(()=>{
if (updateValue) {
// update your screen juse by setting some states on the current component.
} else {
// don't update
}
}, [updateValue])
if you dispatch true value for updateValue whereever in your app then screen-A would be updated regardless it's getting focus or not. if you want to update screen-A only when it's on focus you should add focus condition in useEffect.
I got 3 pages
homepage, productList and productDetails
When going from homepage to productList I pass a route param,
navigation.navigate('productList', { showCategory: 'productListA'} )
InitialProcess when component mounted
Inside the productList page when the component is mounted. I am declaring use state like this.
const {showCateory} = route.params;
const [activeTab, setActiveTab] = useState(showCateory);
and calling api using that activeTab
useEffect(() => {
const unsubscribe = navigation.addListener('focus', () => {
async function fetchData() {
try {
await dispatch(
fetchProductList(
activeTab,
),
);
} catch (err) {
console.log(err);
}
}
fetchData();
});
return unsubscribe;
}, []);
User Interaction
But I also add the button in the productList so that user can change the current active tab
<TouchableOpacity onPress={() => changeTab()}></TouchableOpacity>
const changeTab = async () => {
await setActiveTab('productListB'),
await dispatch(fetchProductList(activeTab)
}
Take note that right now active tab and data coming from api is different from when the component is start mounted.
Navigation Change again
When use goes from productList to productDetails. All thing is fine.
But inside the product details I am going back to productList with this.
navigation.goBack().
When I am back in productList page The activeTab is change back to productListA and the data is change back to when component is mounted
Can I pass or change the route params when calling navigation.goBack()?
add activeTab in useEffect depedineces.
as docs say
The array of dependencies is not passed as arguments to the effect function. Conceptually, though, that’s what they represent: every value referenced inside the effect function should also appear in the dependencies array. In the future, a sufficiently advanced compiler could create this array automatically.
useEffect(() => {
const unsubscribe = navigation.addListener('focus', () => {
async function fetchData() {
try {
await dispatch(
fetchProductList(
//this value will always updated when activeTab change
activeTab,
),
);
} catch (err) {
console.log(err);
}
}
fetchData();
});
return unsubscribe;
}, [activeTab]); //<<<<< here
also you need to know setState() does not always immediately update the component. see here
so change this
const changeTab = async () => {
//await setActiveTab('productListB'),
//await dispatch(fetchProductList(activeTab)
setActiveTab('productListB')
dispatch(fetchProductList('productListB'))
}
This might be happening because route.params is still set to { showCategory: 'productListA'} when you are coming back to the screen.
If this is the case, you can fix it by Changing params object in changeTab() like
navigation.setParams({
showCategory: 'productListB',
});
I hope this will fix your problem.
This happens because the callback function inside the focus listener uses the initial value of the state when the function was defined (at initial page render) . Throughout the lifespan of listener the callback function uses this stale state value.You can read more about this behaviour in this answer
Although the answer by Ahmed Gaber works in this case as the listener is cleared and redefined after each state change.Another common work-around is to use an useRef instead of useEffect.A ref is basically a recipe that provides a mutable object that can be passed by reference.
In your case you can initialise activeTab with navigation param value using useRef hook as :
const activeTab = useRef(showCateory);
and the focus listener callback function should be changed to use the Reference current value as
useEffect(() => {
const unsubscribe = navigation.addListener('focus', () => {
async function fetchData() {
try {
await dispatch(
fetchProductList(
activeTab.current, //<<<<<<---------here
),
);
} catch (err) {
console.log(err);
}
}
fetchData();
});
return unsubscribe;
}, []);
and the changeTab function can directly update reference current value
const changeTab = async () => {
setActiveTab.current = 'productListB';
dispatch(fetchProductList('productListB'))
}
I want to run method when focus screen, i use this:
useEffect(() => {
const unsubscribe = navigation.addListener('focus', () => {
console.log(
'test'
);
});
return unsubscribe;
}, [navigation]);
but it doesnt work. it gives an error like this :
*
An effect function must not return anything besides a function, which
is used for clean-up. You returned: [object Object]
also even i dont return anything, console.log(
'test'
) doest work
I am using navigation V4
Is this working?
import { useFocusEffect } from '#react-navigation/native';
......
useFocusEffect(useCallback(() => {
......
console.log(something)
}, [something]));
//-------
If not check if react navigation is configured correctly.
UPDATE
In React navigation 4.x you will have to follow one of the methods in this guide https://reactnavigation.org/docs/4.x/function-after-focusing-screen/
For useEffect to work properly, the flow is following:
in the square brackets in the end you add a variable which triggers the action. In your case it only triggers on the firs run, and on navigation variable change
you should run your function within useEffect. You have only defined a constant in the body of useEffect, but you never run it.
optionally you may return a function in the end of a run. This function is triggered only when the component unmounts, and used to avoid memory leaks.
Based on this: I'm not sure what are you trying to achieve (unclear from your original post), but this may be what you want:
useEffect(() => {
navigation.addListener('focus', () => {
console.log(
'test'
);
});
const unsubscribe = () => navigation.removeListener('focus'); // !!! I'm not sure about this one, check the docs how to unsubscribe !!!
return unsubscribe;
}, [navigation]); // << triggers useEffect
Assuming you are using the latest version of react-navigation you must the use-focus-effect.
https://reactnavigation.org/docs/use-focus-effect/
Your code should be updated as mentioned below
useFocusEffect(
useCallback(() => {
const unsubscribe = () => {
console.log("test");
}
return () => unsubscribe();
}, [userId])
);
I'm trying to test the OnDidFocus event in my React Native app using react navigation 4 and using the following event listener:
useEffect(() => {
const willFocusSub = props.navigation.addListener(
"onDidFocus",
console.log("testing onDidFocus")
);
return () => {
willFocusSub.remove();
};
});
When I first load the page it works fine but when I move away and then come back to the same screen through the Back button it does not seem to perceive the focus event.
This is my stack
const MovieNavigator = createStackNavigator(
{
MoviesList: HomeMovies,
MovieDetail: MovieDetailScreen,
PopularMovies: PopularMoviesScreen,
CrewMember: CastDetailScreen,
GenreSearch: GenreSearchScreen,
MovieSearch: MovieSearchScreen,
},
I'm in MoviesList and the event is triggered fine, then I move to MovieDetail. If I hit Back and return to MoviesList the event onDidFocus is not triggered at all.
I think you could try "willFocus" instead.
Like this:
const willFocusSub = props.navigation.addListener(
"willFocus",
()=>{console.log("testing willFocus")}
);
Try modyfying your useEffect call to this!
useEffect(() => {
const willFocusSub = props.navigation.addListener(
"onDidFocus",
console.log("testing onDidFocus")
);
return () => {
willFocusSub.remove();
};
},[]);
I found another way to detect the focus and blur event and seems the only way to track an event when using the Back button.
Instead of subscribing to events, I'm check the focus status of the screen using the useIsFocused() hooks available from react-navigation-hooks library.
import { useIsFocused } from "react-navigation-hooks";
...
const [showGallery, setShowGallery] = useState(true);
...
useEffect(() => {
if (isFocused) {
setShowGallery(true);
} else {
setShowGallery(false);
}
console.log("isFocused: " + isFocused);
}, [isFocused]);
Basically I'm checking the status of the screen using isFocused hook every time it changes (when it leaves and returns only same as didFocus and didBlur) and setting the state setShowGallery accordingly to run the carousel when focused and stop it when blurred.
Hope it helps others!
I'm using React Native's Navigator. Is there anyway to refresh the component so when I pop back to it, it'll make a new API call and grab the updated data to display in the component. I found a few similar questions, but no good answer...
Adding Api Call in callBack using a subscription. sovles the issue
componentDidMount() {
this.props.fetchData();
this.willFocusSubscription = this.props.navigation.addListener(
'willFocus',
() => {
this.props.fetchData();
}
);
}
componentWillUnmount() {
this.willFocusSubscription.remove();
}
You can send a callback function to nextscene from previous one as a prop.
this.props.navigator.push({
name: *nextscene*,
passProps: {
text: response,
callBack: this.callback
});
async callback(){
await ....//make new api request grab the udpated data
}
Then in your nextscene you call callback method and then pop. You can also send parameters
this.props.callBack()
this.props.navigator.pop()
When pop () to refresh before a page is not a good idea
You can try DeviceEventEmitter object
previous page DeviceEventEmitter.addListener('xxx', callback) in componentDidMount
current page DeviceEventEmitter.emit('xxx', anythingInCallback...) before pop()
ps:previous pageDeviceEventEmitter.removeAllListeners('xxx') in componentWillUnmount
I doubt you're still looking for an answer to this, but holy crap has this kept me up tonight. I'm very new to React Native, but I finally had some success.
The React Navigation API docs have a section for adding event listeners! Check it out! I shared some of my own code below too.
This is an example event handler in a Component that is the top screen of the StackNavigator stack. It grabs the current state and saves to the backend using an API call. After completion, StackNavigator's pop is called.
handleSubmit = () => {
const { value, otherValue } = this.state
addThingToDatabase({ value, otherValue })
.then(() => this.props.navigation.pop())
}
Now over to the other Component which is the screen "underneath" in the StackNavigator stack. This is screen being shown after the "pop". Here's what I used to have in ComponentDidMount.
componentDidMount() {
const { index } = this.props.navigation.state.params
getAllThingsFromDatabase({ index })
.then(({ arrayOfThings }) => this.setState({
index,
arrayOfThings
}))
}
But the Component wouldn't update with the new thing, until addListener! Now I have pretty much the same code except it's in the constructor. I figured I only need to run it one time, and I need to store it too.
constructor(props, context) {
super(props, context)
this.state = {
index: null,
arrayOfThings: []
}
this.willFocusSubscription = this.props.navigation.addListener(
'willFocus',
(payload) => {
const { index } = payload.state.params
getAllThingsFromDatabase({ index })
.then(({ arrayOfThings }) => this.setState({
index,
arrayOfThings
}))
}
)
}
Note that the docs also mention unsubscribing the event listener using the .remove() function. I put that in ComponentWillUnmount().
componentWillUnmount() {
this.willFocusSubscription.remove()
}
There are four different events to subscribe to. I went with willFocus thinking it'll update before the screen is seen.
You should save the state of the page and emit an action in componentDidMount since it is invoked immediately after a component is mounted.
References:
https://facebook.github.io/react/docs/react-component.html
https://github.com/ReactTraining/react-router
ADDED
Since your component has been already mounted you should listen ComonentWillReceiveProps instead.
The simple way is to use react native navigation resetTo function. It will replace the top item and pop to it.
If we do like this componentWillReceiveProps will call. So we can provide the API calls in that function and make it simple.
for more details https://facebook.github.io/react-native/docs/navigatorios.html#resetto