StackNavigator Back Swipe Callback - react-native

Is there a way to invoke a function when the back swipe gesture is performed?
My view has a custom back button on the top left corner of the navbar. When that button is pressed, I clean up code, then the view is popped. I'm looking to perform this same clean up code when the StackNavigator's back gesture is performed on this particular view. Is this possible using the default back swipe gesture?

You can subscribe to the navigation listeners, on the particular screen where you want to perform the operation.
The transitions can be listened by
const didBlurSubscription = this.props.navigation.addListener(
'didBlur',
payload => {
console.debug('didBlur', payload);
}
);
The JSON object for the payload will look like this
{
action: { type: 'Navigation/COMPLETE_TRANSITION', key: 'StackRouterRoot' },
context: 'id-1518521010538-2:Navigation/COMPLETE_TRANSITION_Root',
lastState: undefined,
state: undefined,
type: 'didBlur',
};
therefore you can add a conditional based on the lastState
Also dont forget to remove the listener
didBlurSubscription.remove();

Related

Pass params to previous screen on swipe/gesture

I've thoroughly read the documentation on passing params between screens with React Navigation: https://reactnavigation.org/docs/params/
However, all of those examples only work if you are calling navigation.navigate manually and passing the params. For example:
<Button
title="Done"
onPress={() => {
// Pass and merge params back to home screen
navigation.navigate({
name: 'Home',
params: { post: postText },
merge: true,
});
}}
/>
I have a screen with a back button, where I can call navigation.navigate and pass params on button press, like in the example above. However, the user can also swipe from the left to go back to the first screen on Android (and I'm assuming iOS as well).
So, my question:
Is there a way for me to pass the same data to the previous screen when the user swipes to go back (instead of pressing the back button)?
There might be a better way that I am unaware of. However, we can achieve this manually by preventing the default back action and handling this ourselves.
Suppose that you have a screen B and you want to swipe back to go to a screen Home and pass params to Home from B on that swipe action. Then, you can achieve this as follows.
function B({navigation}) {
React.useEffect(() => navigation.addListener('beforeRemove', (e) => {
// the navigation.navigate will fire beforeRemove which causes an infinite loop. we guard this here
if (e.data.action.type === "NAVIGATE") {
return
}
// Prevent default behavior of leaving the screen
e.preventDefault();
// navigate manually
navigation.navigate({
name: 'Home',
params: { post: postText },
merge: true,
});
}), [navigation]);
return ( ... )
}
Edit: The navigation.navigate fires the beforeRemove event again, obviously, which causes an infinite loop. We can guard this as shown above.

How unmount a hook after going to new screen with navigate

The context is a simple React Native app with React Navigation.
There are 3 screens.
The first simply displays a button to go to second screen using navigation.navigate("SecondScreen").
The Second contains a hook (see code below) that adds a listener to listen the mouse position. This hook adds the listener in a useEffect hook and removes the listener in the useEffect cleanup function. I just added a console.log in the listener function to see when the function is triggered.
This screen contains also a button to navigate to the Third screen, that only shows a text.
If I go from first screen to second screen: listener in hook start running. Good.
If I go back to the first screen using default react navigation 's back button in header. the listener stops. Good.
If I go again to second screen, then listener runs again. Good.
But if I now go from second screen to third screen, the listener is still running. Not Good.
How can I unmount the hook when going to third screen, and mount it again when going back to second screen?
Please read the following before answering :
I know that:
this is due to the fact that react navigation kills second screen when we go back to first screen, and then trigger the cleanup function returned by the useEffect in the hook. And that it doesn't kill second screen when we navigate to third screen, and then doesn't trigger the cleanup function.
the react navigation's hook useFocusEffect could be used to resolve this kind of problem. But it can't be used here because it will involve to replace the useEffect in the hook by the useFocusEffect. And I want my hook to be usable in every context, even if react navigation is not installed. More, I'm using here a custom hook for explanation, but it's the same problem for any hook (for example, the native useWindowDimensions).
Then does anyone know how I could manage this case to avoid to have the listener running on third screen ?
This is the code of the hook sample, that I take from https://github.com/rehooks/window-mouse-position/blob/master/index.js, but any hook could be used.
"use strict";
let { useState, useEffect } = require("react");
function useWindowMousePosition() {
let [WindowMousePosition, setWindowMousePosition] = useState({
x: null,
y: null
});
function handleMouseMove(e) {
console.log("handleMouseMove");
setWindowMousePosition({
x: e.pageX,
y: e.pageY
});
}
useEffect(() => {
window.addEventListener("mousemove", handleMouseMove);
return () => {
window.removeEventListener("mousemove", handleMouseMove);
};
}, []);
return WindowMousePosition;
}
module.exports = useWindowMousePosition;
the react navigation's hook useFocusEffect could be used to resolve this kind of problem. But it can't be used here because it will involve to replace the useEffect in the hook by the useFocusEffect. And I want my hook to be usable in every context, even if react navigation is not installed
So your hook somehow needs to know about the navigation state. If you can't use useFocusEffect, you'll need to pass the information about whether the screen is focused or not (e.g. with an enabled prop).
function useWindowMousePosition({ enabled = true } = {}) {
let [WindowMousePosition, setWindowMousePosition] = useState({
x: null,
y: null
});
useEffect(() => {
if (!enabled) {
return;
}
function handleMouseMove(e) {
console.log("handleMouseMove");
setWindowMousePosition({
x: e.pageX,
y: e.pageY
});
}
window.addEventListener("mousemove", handleMouseMove);
return () => {
window.removeEventListener("mousemove", handleMouseMove);
};
}, [enabled]);
return WindowMousePosition;
}
And then pass enabled based on screen focus:
const isFocused = useIsFocused();
const windowMousePosition = useWindowMousePosition({ enabled: isFocused });
Note that this approach will need the screen to re-render when it's blurred/focused unlike useFocusEffect.

Expo React Native execute function when entering view

I'm trying to make a function execute when a view is in foreground, but just once not on each update of the component. If the user navigates to another view and goes back to the first view it should execute that function again, but just once. Is there a solution to this?
if using useEffect without second parameter it executes on each update, if I add [] as second parameter it only executes the first time the view is rendered but not when navigating back to it.
Any help appreciated!
if you are using react-navigation you can do this by listen on screen focus see here
React.useEffect(() => {
const unsubscribe = navigation.addListener('focus', () => {
// The screen is focused
// Call any action
});
// Return the function to unsubscribe from the event so it gets removed on unmount
return unsubscribe;
}, [navigation]);

I am using Backhander in react native with react-native-router-flux but its reacting on all screens where I want to make it work for screen specific

I am using Backhander in react native with react-native-router-flux but it's reacting on all screens where I want to make it work for screen-specific, but when I am trying to get the current route name in the onBackPress method, it's giving me first screen name in router name.
componentDidMount() {
BackHandler.addEventListener('hardwareBackPress', this.onBackPress);
}
componentWillUnmount() {
BackHandler.removeEventListener('hardwareBackPress', this.onBackPress);
}
onBackPress = () => {
alert(this.props.navigation.state.routeName)
}
First of all - BackHandlers in React Native are global and not screen specific. But you can achieve your wanted behavior.
Some background
With BackHandler.addEventListener you push an event listener on a Stack of event listeners, with BackHandler.removeEventListener you remove the given listener from the Stack. When the BackButton is pressed, the top listener from the stack is called and the code is executed. Then the next listener is called and so on. This stops when the first listener returns true.
For your specific problem
You should ensure that you add an event listener on the page you want it to (like you are doing in your code example)
You should ensure that your event listener returns true
You should ensure that your listener gets removed when unmounting the view (like you do)
Now you BackHandler should work for the view you have implemented it in (lets call it view1). But you have to think about all the other views. Especially when you are pushing views on top of view1. Ether you can implement an "onFocus" and "onBlur" method for view1 and use this methods instead of componentDidMount and componentWillUnmount for adding and removing event listeners, or you have to add event listeners for the back handler for all views that are pushed on top of view1.
Hope that helps :-)
Source: https://facebook.github.io/react-native/docs/backhandler
If you want backHandler to act differently for specific screen then you can use Actions.currentScene in your onBackPress function :
onBackPress = () => {
if(Actions.currentScene === 'SceneKey'){
return true;
}
Actions.pop();
return true;
}

how can i send callback to my parent view when call Actions.pop in react-native-router-flux

I am using https://github.com/aksonov/react-native-router-flux for navigation in react native.How can I send callback to my parent view when I call pop actions on it.
onPressed() {
Actions.pop();
}
This is how I call pop action on it but I need to send updated value to previous view.
will I finally solve it by calling empty refresh with props after delay
Actions.popTo('pageOne');
setTimeout(() => {
Actions.refresh({name:'zzzzar'});
console.log("zzzz");
}, 10);