I wanted to run a function whenever I change to a specific tab in my react-native app, but I couldn't find a way.
I know that after Wix Navigation (RNN) v2, the way to change current tab is by using mergeOptions, and that works. But what if I want to also, after the tab is changed, execute a function from that screen?
This is how I'm changing the tab:
Navigation.mergeOptions(this.props.componentId, {
bottomTabs: {
currentTabIndex: 3
}
}
The tab is changed, but I couldn't find a way to add a listener or trigger event after this specific event occurs.
Is there any way to accomplish this?
React-Native: 0.60.5
React-Native-Navigation: 3.1.2
Thanks.
You can add a listener which will trigger when ever the tab change . See Documentation
Try this
// Subscribe
const bottomTabEventListener = Navigation.events().registerBottomTabSelectedListener(({ selectedTabIndex, unselectedTabIndex }) => {
});
...
// Unsubscribe
bottomTabEventListener.remove();
Related
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.
I'm trying to handle the back button on android since my React Native app has custom logic on the back button pressed for the root screen...Is there any method like Navigation.getCurrentRoutes()
in order to do something like this:
handleBackButton = () => {
if(Navigation.getCurrentRoutes().size()>1) {
return true; // addEventListener listens for the native hardware back button press and will ignore it
}
... customLogic
return false; // will execute the native back press (and exit the app)
}
"react-native-navigation": "3.0.0-alpha.2"
It turns out that there is not such a method and the two options are:
1) Handle back buttons on your own (overriding) or
2) just add your custom logic into componentWillUnmount() and continue using the native react-native-navigation
options 2 worked for me
reference: https://github.com/wix/react-native-navigation/pull/4226#issuecomment-433683885
https://github.com/wix/react-native-navigation/issues/4231
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;
}
When using startTabBasedApp(params) in React Native Navigation (by Wix), all of the tabs load at once.
Is there a way to make it so that only the initial tab loads? And then only after clicking on another tab does that tab load?
I don't believe it's possible. See this comment for more information, it seems like they intended this behaviour.
A possible workaround is to use the following within the component:
onNavigatorEvent(event) {
switch (event.id) {
case "bottomTabSelected":
this.setState({ didPressTab: true })
break
case "willAppear":
ApplicationScreensManager.getInstance().registerCurrentActiveScreen(ApplicationScreens.WebViewContainerScreen)
}
break
}
}
while this case "bottomTabSelected": is fired only when selecting a tab, then you can set the state raising a flag and only then do whatever you intended on when pressing the tab.
good luck
I am new to react native and new to iOS (not programming) so please excuse me if this question is a simple one. I am trying to navigate from one view to another (with a transition), however they are not related so I do not need the back navigation. I actually do not have a navigation bar at all. When using the Navigator component it seems to not support this at all. I am not sure if there is a separate way to do this but I am not able to figure it out without implementing my own hack.
If I use the navigator component and keep pushing on the views then it just keeps them all in memory and I do not want that. I can transition from one view to another and then pop but I may end up going to the wrong view in that case. I can also replace the view but it seems that does not allow for transitions.
To give you a scenario think of it like this:
Application starts and loads a "Loading" screen.
When initial loading is complete it will then go to the "Login" screen.
There is a button on the "Login" screen to "Register" or "Retrieve Password".
If they click "Register" it will take them there with a button back to "Login".
If they click "Retrieve Password" it will take them to a page with buttons to go back to "Login" or "Register".
So by this example you can see that there is no way to pop because if you were on the login screen and went to the register screen and then wanted to go the retrieve password screen then pop just simply wouldn't work. I do not want any navigation controls on the screen I just want to be able to do a smooth transition between these screens.
Now I was able to find a way to do this but I had to add a method to the Navigator class and hack code in using some of there core methods which seems like its not a good idea at all but here is the code (note this is really just a hack to see if it would work):
Navigator.prototype.pushWithUnmount = function(route) {
var activeLength = this.state.presentedIndex + 1;
var activeStack = this.state.routeStack.slice(0, activeLength);
var activeAnimationConfigStack = this.state.sceneConfigStack.slice(0, activeLength);
var nextStack = activeStack.concat([route]);
var destIndex = nextStack.length - 1;
var nextAnimationConfigStack = activeAnimationConfigStack.concat([
this.props.configureScene(route),
]);
this._emitWillFocus(nextStack[destIndex]);
this.setState({
routeStack: nextStack,
sceneConfigStack: nextAnimationConfigStack,
}, () => {
this._enableScene(destIndex);
this._transitionTo(
destIndex,
null, // default velocity
null, // no spring jumping
() => {
this.replaceAtIndex(nextStack[destIndex], 0);
this.setState({
presentedIndex: 0,
});
}
);
});
}
By using the code provided above I am now able to do:
this.props.navigator.pushWithUnmount({ component: SomeComponent });
With this code the views are pushed onto the stack with a transition and the old views are unmounted when its finished.
Please tell me that I am doing something wrong and that there is a better way to do this?
The default router with React Native is pretty limited. I'd check out React Native Router Flux. We just switched to it a few weeks ago in our product and have really liked it. It does exactly what you want.