React-native: Go back to a specific screen in stack - react-native

This is the root navigator
export const AppNavigator = StackNavigator({
Splash: { screen: Splash },
Dashboard: { screen: DashboardDrawer }
});
const DashboardDrawer = DrawerNavigator({ DashboardScreen: {
screen: StackNavigator({
A: { screen: A },
B: { screen: B },
C: { screen: C },
D: { screen: D },
}
}, {
contentComponent: DashboardDrawerComponent,
drawerWidth: 280
});
I have 4 screens - A, B, C, D in my stack.
I want to jump from D to A. (or D to any screen)
I referred to the following react-navigation documentation-https://reactnavigation.org/docs/navigators/navigation-prop#goBack-Close-the-active-screen-and-move-back
The above doc. states that to go from screen D to screen A (popping D, C, and B) you need to supply a key to goBack FROM, in my case B, like this
navigation.goBack(SCREEN_KEY_B)
So, my question is from where should I get the key for a specific screen?
I checked my root navigation object, and it shows me some dynamically generated key for each screen. How can I specify my own keys for the screens?

It was tricky!
I referred to this section of react-navigation documentation, and have achieved the above!
https://reactnavigation.org/docs/routers/#Custom-Navigation-Actions
And this is how,
1. Altered my DrawerNavigator in the question, a little (to fit in the the following stacknavigator)
const DrawerStackNavigator = new StackNavigator({
A: { screen: A },
B: { screen: B },
C: { screen: C },
D: { screen: D },
}
});
const DashboardDrawer = DrawerNavigator({
DashboardScreen: DrawerStackNavigator,
}, {
contentComponent: DashboardDrawerComponent,
drawerWidth: 280
});
2. Dispatched an action in screen D
const {navigation} = this.props;
navigation.dispatch({
routeName: 'A',
type: 'GoToRoute',
});
3. Listened to this action on my stack navigator
const defaultGetStateForAction = DrawerStackNavigator.router.getStateForAction;
DrawerStackNavigator.router.getStateForAction = (action, state) => {
if (state && action.type === 'GoToRoute') {
let index = state.routes.findIndex((item) => {
return item.routeName === action.routeName
});
const routes = state.routes.slice(0, index+1);
return {
routes,
index
};
}
return defaultGetStateForAction(action, state);
};

Looks like the way they are doing it right now is to save the key of the screen you want to come back through params. So once you are in B and navigate to C, you pass the screen key to C as a param and then when you navigate from C to D you pass the key of B to D as a param.
More info here.

I use a setup with NavigatorService as outlined here in the post from unexge.
From the service, I expose the following:
function goBackToRouteWithName(routeName: string) {
if (!_container) return;
const route = _getRouteWithName(_container.state.nav, routeName);
route && _container.dispatch(NavigationActions.back({ key: route.key }));
}
function _getRouteWithName(route, name: string): ?NavigationRouteConfigMap {
const stack = [];
stack.push(route);
while (stack.length > 0) {
let current = stack.pop();
if (current.routes) {
for (let i = 0; i < current.routes.length; i++) {
if (current.routes[i].routeName === name) {
//NOTE because of going from, not to!!
return current.routes[i + 1];
}
stack.push(current.routes[i]);
}
}
}
}
this works well for me.

Related

React Native Navigation different animation depending on origin

I'm trying to get the following to work.
There are 3 screens:
Screen A
Screen B
Screen C
I want a different animation for when A goes to C than when B goes to C. Does anyone know how to do that?
You can use this library : rn-transitions . SO as per the docs you can add specific transitions to specific pages like :
import { fromLeft, zoomIn, zoomOut } from 'react-navigation-transitions'
const handleCustomTransition = ({ scenes }) => {
const prevScene = scenes[scenes.length - 2];
const nextScene = scenes[scenes.length - 1];
// Custom transitions go there
if (prevScene
&& prevScene.route.routeName === 'ScreenA'
&& nextScene.route.routeName === 'ScreenB') {
return zoomIn();
} else if (prevScene
&& prevScene.route.routeName === 'ScreenB'
&& nextScene.route.routeName === 'ScreenC') {
return zoomOut();
}
return fromLeft();
}
const PrimaryNav = createStackNavigator({
ScreenA: { screen: ScreenA },
ScreenB: { screen: ScreenB },
ScreenC: { screen: ScreenC },
}, {
transitionConfig: (nav) => handleCustomTransition(nav)
})
hope this helps. feel free for doubts

React Native first time user stacknavigator

I have a stacknavigator with a intro screen and a login screen.
export const StackerIntro = createStackNavigator({
Intro: {
screen: Intro,
navigationOptions: {
header: null
}
},
Login: {
screen: Login,
}
}, {
InitialRouteName: "Intro"
})
How can I make the initial route be "Login" if it isnt the users first time in the app?
For conditional rendering you might have to use switchNavigation.
1 .Save the key in async storage if the user logs in for the first time.
When the user comes again for the second time, get the key in navigation class using async-await and if you get the key then navigate to the desired location or else navigate to login.
Use SwitchNavigation for conditional navigation.
You can also try :
const routeName = () => {
var routeName =
global.isSignUpScreen == false || global.isSignUpScreen == undefined
? "LoginScreen"
: "SignUpScreen";
console.log("routeName >> " + routeName);
return routeName;
};
const SignUpStack = createStackNavigator(
{
LoginScreen: {
screen: LoginScreen,
navigationOptions: {
header: null
}
},
SignUpScreen: {
screen: SignUpScreen,
}
{
initialRouteName: routeName()
}
}
);
you dont need to set initialroute to be login. all you need to do is store a value in asyncstorage when the app is opened for the first time and for the second time when it comes to app.js check if that value is there are not. if its there then you need to go to login else go to intro.
Ex:
const value = await AsyncStorage.getItem("installSucess");
if(value === 'true'){
navigate to login
}else{
navigate to into
}
and in intro screen for the first time when you installed the app store value in asynstorage like
await AsyncStorage.setItem("installSucess", "true");

react-navigation: access stateChange event of nested navigator

I'm trying to add a state change event to a nested navigator.
Usually I would define it like this:
const TabNavigator = createMaterialTopTabNavigator({
Home: {
screen: Home,
},
Next: {
screen: NextNavigator,
},
})
but I need something like this:
const TabNavigator = createMaterialTopTabNavigator({
Home: {
screen: Home,
},
Next: {
screen: (props)=>{ return <NextNavigator {...props}/> },
},
})
where the screen object is a function.
The problem is I have no idea what signature screen as a function has, and how to pass it to the navigator
I'm getting close with something like this:
screen: (props, state)=>{ return <NextNavigator {...props} state /> },
But it's still not correct.
Does anyone know how this works?
If you don't want #JeffGuKang to answer, you can declare and use global variables and functions.
let NICKNAME = '';
function setNickName(data) {
NICKNAME = data;
}
function getNickName() {
return NICKNAME;
}
export { setNickName,getNickName }
...
//set data
import { setNickName} from './globalpath';
setNickName(data)
....
//get data
import { getNickName} from './globalpath';
getNickName()
Use Params
Use setParams, getParam for changing state in components wrapped by navigatior.
A Component (Set changing value)
const name = this.props.navigation.setParams({ name: this.state.name, age: this.state.age });
B Component (Get value)
const name = this.props.navigation.getParam('name', 'DefaultValue');
const age = this.props.navigation.getParam('age' 0);
setParams is a trigger to run getParam in other components is wrapped navigation.
Extend Navigator
You can extend the component created by Naivgator as below. link
const MyStack = createStackNavigator({ ... });
class CustomNavigator extends React.Component {
static router = MyStack.router;
state = {
a: 'b',
}
render() {
const { navigation } = this.props;
return <MyStack {...this.state, ...this.props} />;
}
}
Or it is able to use functional component instead.
const MyStack = createStackNavigator({ ... });
CustomNavigator = (props) => {
return <MyStack {...props} />
}
const TabNavigator = createMaterialTopTabNavigator({
Home: {
screen: Home,
},
Next: {
screen: (props) => { return <MyStack {...props} /> },
},
Third: CustomNavigator,
})
And do not forget import `React` to use functional component.

Set initialRouteName dynamically in React Navigation TabNavigator

I want to set initialRouteName of my TabNavigator dynamically in splash screen. In splash, i decide initialRouteName based on an api result but i don't know how to pass this route name to my router file.
Router.tsx:
public static Routes = StackNavigator({
Splash: { screen: Splash },
Verification: { screen: Verification },
Login: { screen: Login },
Main: {
screen: TabNavigator({
Calendar: { screen: Calendar },
PendingReserves: { screen: PendingReserves },
Profile: { screen: Profile },
},
{initialRouteName: 'Calendar'}) // I want to set it from splash
}
}, {initialRouteName: 'Splash'} );
splash.tsx:
constructor(props: SplashScreenProps, context: any) {
super(props, context);
this.state = {
Error: false
}
this.check()
}
check = async () => {
let pending = await dataService.getPendingReserves(data);
if (pending.Success) {
if (pending.result.length > 0) {
// go to pendingReserve Tab
} else {
// go to Calendar Tab
}
} else {
this.setState({ Error: true })
}
}
I tried to route statically to PendingReserves and in it's constructor, check the api and change tab if necessary but i couldn't change tab programmatically with this code:
pendingReserves.tsx:
fetchData = async () => {
let res = await Utilities.getPendingReserves()
if (res && res.length > 0) {
...
} else {
const resetAction = NavigationActions.reset({
index: 0,
actions: [NavigationActions.navigate({ routeName: 'Calendar' })]
})
this.props.navigation.dispatch(resetAction)
// this.props.navigation.navigate({ routeName: 'Calendar' })
// both reset and simple navigate are not works correctly...
}
}
So any solution for dynamically set initialRouteName OR change tab programmatically will help me. Thanks
I'm using:
"react-native": "0.49.3",
"react-navigation": "^1.0.0-beta.13"
Finally... I didn't find a way to change tab programmatically in react navigation version 1, so i update to version 2 and this.props.navigation.navigate('Calendar') works correctly.

React navigation how to navigate to children through parent?

So I have this react navigation setup:
Stack navigator
|Tab navigator
| | Screen_A
| | Screen_B
| Screen_C
| Screen_D
Normal case, user would go to screen A, then click to go to screen C with some params. From screen B, user click to go to screen D.
Now on screen D, when user click, I need him to go to screen C but through screen A (because when user click back from C, they need to go back to screen A).
I tried to pass params to screen A from screen D, and in A's render() method, check for params and continue navigate to screen C, and it works. But I got warning "Cannot update during an existing state transition(such as within 'render'...". So how can I accomplish this without triggering any warning?
Thanks
A possible alternative solution would be to leverage the customized stack router, here I have came up with a possible solution.
In this case, when you navigate from ScreenD to ScreenC, you can directly navigate to ScreenC, but then when the user on ScreenC and try to go back, we will insert ScreenA into the stack so the user will see ScreenA instead of ScreenD.
const NestedNavigator = TabNavigator({
ScreenA: { screen: ScreenA },
ScreenB: { screen: ScreenB },
});
const MainNavigator = StackNavigator({
Home: {
screen: NestedNavigator
},
ScreenC: { screen: ScreenC },
ScreenD: { screen: ScreenD },
});
const defaultGetStateForAction = MainNavigator.router.getStateForAction;
MainNavigator.router.getStateForAction = (action, state) => {
if (state && action.type === 'Navigation/BACK' && state.routes[state.index].routeName === 'ScreenC') {
const routes = [
...state.routes,
{index: 0, key: 'ScreenA', routeName: 'Home', routes: [{ key: 'ScreenA', routeName: 'ScreenA'}, { key: 'ScreenB', routeName: 'ScreenB' }]},
];
return {
...state,
routes,
index: routes.length - 1,
};
}
return defaultGetStateForAction(action, state);
};