await for react-navigation transition to finish - react-native

I'm looking for a way in react-navigation to dispatch a navigation action and await for it to be fully finished before triggering the next step.
Something like:
await dispatch({
type: 'Navigation/goBackTo',
routeName: 'Main',
})
dispatch(anotherAction())
Currently I'm using a setTimeout workaround.

Not specifically related to react-navigation, but useful nonetheless: the InteractionManager API from react-native.
There is a method runAfterInteractions() which will be called after all animations have been completed, so in case of navigation I find it to be a handy tool. For example you could do something like this:
class Main extends Component {
componentDidMount() {
// 1: Component has been mounted off-screen
InteractionManager.runAfterInteractions(() => {
// 2: Component is done animating
// 3: Do your anotherAction dispatch() here
});
}
}

If you are using a StackNavigator you can use transitionEnd event
React.useEffect(() => {
const unsubscribe = navigation.addListener('transitionEnd', (e) => {
// Do something
});
return unsubscribe;
}, [navigation]);

Maybe you can subscribe to the didFocus event of react-navigation to trigger the second action.
Read about it here:
https://reactnavigation.org/docs/navigation-prop.html#addlistener-subscribe-to-updates-to-navigation-lifecycle

Related

Why use Hub.listen inside useEffect - Amplify Auth

I'm looking through the documentation at https://docs.amplify.aws/lib/auth/social/q/platform/js/#full-samples
and can't understand why Hub.listen is being used within use Effect.
Hub.listen('auth', ({ payload: { event, data } }) => {
switch (event) {
case 'signIn':
case 'cognitoHostedUI':
getUser().then(userData => setUser(userData));
break;
}
});
If I'm creating an event listener why should I create in useEffect instead of in the main body of the function.
What am I misunderstanding?
Figured it out, I was getting confused between functions and classes.
The useEffect with an empty array is being used to only create the event listener on initial render and not on subsequent renders.
Leaving the question up in case anyone else gets similarly confused
This has little to do with functions and classes. It is inside the useEffect(() => {/*...*/}, []) (note the empty dependency list) as it acts as the constructor of the component and is therefore only called once.
If Hub.listen is called inside the render function, this will result in growing list of event listeners, because everytime the component rerenders, a new listener is attached.
To keep you free from any memory issues, you would also detach a listener inside the useEffect hook. So for common listeners, would look like the following:
const App = () => {
useEffect(() => {
const listener = () => { console.log('foo'); }
window.addEventListener('resize', listener);
// destruct
return () => {
window.removeEventListener('resize', listener)
}
}, []);
}

Is it possible to navigate inside a Redux toolkit action to another screen after an asyncthunk is fullfilled?

So recently I am working on a react native app. I did wonder how I navigate from a fullfiled action?
I did not find away to be able to do that. What i have so far is this:
Dispatch Action
Navigate without knowing the result.
How is this achievable.
Some code:
Dispatch in the Submit function:
dispatch(
createTopic({
title: values.title,
subject: values.subject,
subsubject: values.subSubject,
description: values.description,
uid: userUid
})
, [dispatch])
export const createTopic = createAsyncThunk('topic/createTopic',
async ({title, subject, subsubject, description, uid}) =>{
try {
console.log(uid)
const response = await firebase.firestore().collection("Users").doc(uid).collection("Topics")
.doc()
.set({
title: title,
subject: subject,
subsubject: subsubject,
description: description
})
return response
} catch (error) {
console.log(error)
}
}
)
I'm assuming since this is react-native that you are using react-navigation? It is possible to navigate within the Redux action itself using the methods described in the docs page Navigating without the navigation prop. But it's probably better to initiate the navigation from the component.
The result of dispatching a thunk action is a Promise. Redux toolkit wraps this Promise so that there are no uncaught errors in your component. It always resolves to either a success or failure action. But you can unwrap() the result to use it with a try/catch.
const dispatch = useDispatch();
const navigation = useNavigation();
const handleSubmit = async (e) => {
try {
// wait for the action to complete successfully
const response = await dispatch(createTopic(args)).unwrap();
// then navigate
navigation.navigate(...);
} catch (error) {
// do something
}
}
Note: you should not use a try/catch in your createAsyncThunk. You want errors to be thrown so that they dispatch a 'topic/createTopic/rejected' action.
If you want to navigate from redux action and any were event when you don't have navigation prop, use ref of the navigation container,
eg:
export const navigationRef = createRef()
// when adding NavigationContainer add this ref
ie:
<Navigationcontainer ref={navigationRef}>
{...rest code}
</Navigationcontainer>
now you can use that ref to navigate to other screens
eg:
navigationRef.current?.navigate('ScreenName', params)
use above line the redux action...

OnDidFocus event not working when you navigate back from the stack

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!

How to use useFocusEffect hook

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>;
}

Refresh Component on navigator.pop()

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