How to navigate to a specific screen and disallow going back with react-native-navigation - react-native

I am using react-native-navigation and I have a stack of screens.
When I go from screen A to screen B, I don't want to give the user the option to go back to screen A, just forward.
I am trying Navigation.popTo("screen.B.id") but I'm getting this error:
Is there any way to achieve this? Thanks in advance.

React-navigation
You can reset the stack like this:
const resetAction = StackActions.reset({
index: 0,
actions: [NavigationActions.navigate({ routeName: 'screenB' })],
});
this.props.navigation.dispatch(resetAction);
React-native-navigation
A workaround would be to catch the back listener like so:
import {BackHandler} from 'react-native';
export default class RoomType extends Component {
_didFocusSubscription;
_willBlurSubscription;
constructor(props) {
super(props);
this._didFocusSubscription = props.navigation.addListener('didFocus',payload =>
BackHandler.addEventListener('hardwareBackPress', this.onBackButtonPressAndroid)
);
}
}
componentDidMount() {
this._willBlurSubscription = this.props.navigation.addListener('willBlur', payload =>
BackHandler.removeEventListener('hardwareBackPress', this.onBackButtonPressAndroid)
);
}
componentWillUnmount() {
this._didFocusSubscription && this._didFocusSubscription.remove();
this._willBlurSubscription && this._willBlurSubscription.remove();
}
onBackButtonPressAndroid = () => {
//code when you press the back button
};

You could try with setting Screen B to your new root.
setStackRoot(componentId, params)
Maybe you have to popToRoot(componentId, mergeOptions?) if necessary.
Source: react-native-navigation docs

In react-native-navigation, there are 2 options you could choose from to achieve what I believe you are looking for.
try adding the below to your topBar option specifically in the child component you are choosing.
backButton: {
visible: false
}
for a small example, the child you don't want a back option for put:
component: {
id: 'screenB',
name: 'screenB',
options: {
title: {
text: 'screenB'
},
topBar: {
// the other options you want
backButton: {
visible: false
}
}
}
}
you can completely reset the root navigation to the new screen.
In my Opinion,
Option 1. is a simple way to flat out just remove the back button from a specific screen to disable the ability to ever go back to the original screen.
Option 2. is nice when you want to remove the previous screen from the entire equation of the app itself.
my personal use-case for option 2:
I made an app that originally opens to a login/register stack. Once logged in/ registered, I save that information to the AsyncStorage and completely reset the root to the home page.
When opening the app for the second time, it checks for user info from the AsyncStorage. If the app finds user information, It sets the root for the home page and rest of the app. If the app doesn't find user information, it sets the root to the login/register stack and the cycle continues.
I hope this helps!

Use createSwitchNavigator from 'react-navigation' for both screens,which will not let back button of second screen to switch to first screen and also header will not be there with back arrow.
In your App.js,
import * as React from 'react';
import { Text, View, StyleSheet } from 'react-native';
import { createSwitchNavigator,createAppContainer } from 'react-navigation';
import ScreenOne from './components/ScreenOne ';
import ScreenTwo from './components/ScreenTwo ';
const App=createSwitchNavigator({
ScreenOne :{screen:ScreenOne },
ScreenTwo :{screen:ScreenTwo }
});
export default createAppContainer(App);

Related

refetch usequery when go back to previous screen not working in react native

I have 2 page, Page A (current page) and page B (next page). I am using react-native-router-flux as navigation. When go back to page A from page B (Actions.pop()) i want to refetch usequery so i put code like this in page A or component A
const { loading, data, refetch: refetchData } = useQuery(QUERY_GET_STATUS, {
fetchPolicy: 'network-only',
});
useEffect(() => {
if(refresh){
refetchData();
}
}, [refresh])
variable refresh is redux state has value true and false. Before go back to page A refresh state will be update first into true. but i found the issue that refetch query not working. Do you have any solution to resolve it ?
If you wanna call function every time when screen on front then you this hook
import { useFocusEffect } from '#react-navigation/native';
import React{useCallback} from 'react'
useFocusEffect(
useCallback(() => {
//function
}, [])
);
I had a similar problem with a different package. I'm not totally sure if this might work for you but I think with react-native-router-flux, you have access to currentScene. So you could add an effect that is called whenever the route changes
const currentScene = Actions.currentScene;
useEffect(() => {
if(refresh && currentScene === "whatever-scene-you-are-on"){
refetchData();
}
}, [refresh, currentScene])

How to refresh a screen when returning from another screen of a different navigator (React Native)?

I have been implementing most of my application with a StackNavigator. Now, I added a DrawerNavigator, from which one of its screens calls another screen of the original StackNavigator. For example, consider the following navigation sequence that a user could make:
ScreenA -> ScreenB -> ScreenC
where ScreenA belongs to the StackNavigator, ScreenB belongs to the DrawerNavigator, and ScreenC belongs to the StackNavigator again. To achieve that, actually ScreenA does not call ScreenB directly, but another screen whose sole purpose is to serve as a root of all the screens that belong to the DrawerNavigator. Also, that root receives the StackNavigator in the ScreenProps in order that its screens can later use the Stack again.
Now, if I am in ScreenC and I go back using "this.props.navigation.goBack()", I return to the DrawerNavigator in the ScreenB, because that is which called ScreenC. The ScreenB should refresh its state, that is, it should reload information from the database, because that information could have changed in ScreenC, so the previous state is no longer valid.
When only using StackNavigator, I always managed to do it using "NavigationEvents". For example:
import {Component} from 'react'
...
import { NavigationEvents } from 'react-navigation'
class ScreenB extends Component{
// This is the function that loads information from the database (PouchDB)
loadInformation = async() =>{
...
}
render(){
return(
<View>
<NavigationEvents onWillFocus = {payload => this.loadInformation()}/>
<NavigationEvents onDidFocus = {payload => this.loadInformation()}/>
...
</View>
)
}
}
With this implementation, the function "loadInformation" activated both when I entered the screen for first time, and also when I returned to it from a child screen. But this time that I am mixing both navigators, neither "onWillFocus" nor "onDidFocus" are activating when returning from ScreenC to ScreenB, so I cannot enter to the "loadInformation" function again. How could I do it?
Edit:
I also tried keeping a boolean variable in Redux store that determines if the function "loadInformation" of ScreenB must be activated. That variable starts with the true value. Then, once I enter to Screen B and I execute the function, it is changed to false. When I navigate to ScreenC, in that screen the variable is changed to true again, so when I go back to ScreenB it indicates again that the function must be executed.
That required to use in ScreenB the "componentDidUpdate" function, that constantly checks if that variable is true or false in order to call "loadInformation". That solved the problem, but brought a new one. When I try to navigate from ScreenB to another screen of the DrawerNavigator, it takes too much time, because in the transition "componentDidUpdate" is called repeatedly. So this solution does not seem viable.
Unfortunately the approach you used <NavigationEvents> has been updated. so, what should you do is:
class screenA/ screenB/ screenC extends React.Component {
componentDidMount() {
this._unsubscribe = navigation.addListener('focus', () => {
// do something
});
}
componentWillUnmount() {
this._unsubscribe();
}
render() {
// Content of the component
}
}
Use these updated navigation events in all of your screens. Hope it will solve your issue. For more information See This
I am answering my own question.
The solution was to use a boolean variable from Redux's store that indicates if the function "loadInformation" must be activated or not. Let's say the variable is named "loadView", which has the value "false" by default, but the ScreenC sets it in "true" when it is going to be closed and therefore we are going to return to ScreenB.
In other words, the file of ScreenC includes this code:
import {Component} from 'react'
import { connect } from 'react-redux'
// Here we import the action that allows to change the value of "loadView"
import { changeLoadView } from '../../redux/actions/popUpActions'
...
class ScreenC extends Component{
...
// Function that is activated automatically when we leave the screen
componentWillUnmount(){
// This is the function that assigns the value "true" to "loadView"
this.props.dispatchChangeLoadView(true)
}
...
}
const mapStateToProps = (state) => {
return {
...
}
}
const mapDispatchToProps = (dispatch) => {
return {
dispatchChangeLoadView: (bool) => dispatch(changeLoadView(bool)),
....
}
}
export default connect(mapStateToProps, mapDispatchToProps)(ScreenC);
In the file of ScreenB I use a "fake view", which is a React View that is not placed directly in the "render" function but it is called by another function (named "activateLoadInformation" in this case). That function returns an empty view, but the reason to use it is because before its "return" we can activate any other function of ScreenB that we want, that in this case is "loadInformation". I don't know another way to activate functions that don't render anything arbitrarily when we want to.
import {Component} from 'react'
import { connect } from 'react-redux'
...
class ScreenB extends Component{
...
// This is the function that loads information from the database (PouchDB)
loadInformation = async() =>{
this.props.dispatchChangeLoadView(false);
...
}
// Fake view that calls the function "loadInformation"
activateLoadInformation(){
this.loadInformation();
return(<View/>)
}
render(){
return(
<View>
{!this.props.loadView &&
<NavigationEvents onWillFocus = {payload => this.loadInformation()}/>
}
{this.props.loadView &&
this.activateLoadInformation()
}
...
</View>
)
}
}
const mapStateToProps = (state) => {
return {
loadView: state.popUpReducer.loadView,
}
}
const mapDispatchToProps = (dispatch) => {
return {
dispatchChangeLoadView: (bool) => dispatch(changeLoadView(bool)),
....
}
}
export default connect(mapStateToProps, mapDispatchToProps)(ScreenB);

React Native - Adding logic to Stack Navigation

I have the following Stack Navigation:
const WelcomeStack = createStackNavigator({
UpdateProfileScreen: {
screen: UpdateProfileScreen,
},
SelectProfileScreen: {
screen: SelectProfileScreen,
},
PresentationModal: {
screen: PresentationModal,
}
}, {
initialRouteName: 'UpdateProfileScreen',
headerMode: 'none'
})
When a user is new, I show "UpdateProfileScreen" first, then I move to "SelectProfileSecreen" and then "PresentationModal".
If for some reason after "UpdateProfileScreen" user closes the app, next time they log in I will show "SelectProfileSecreen" and "PresentationModal". If they complete data, next time, they will only see the "PresentationModal"
Since I have set "UpdateProfileScreen" as initialRoute, it will always load first, even if it does not show.
So I was wondering if programatically I could change the initialRoute and/or if I do:
this.props.navigation.navigate("WelcomeStack")
I can add some logic in the same stack to handle the page to show?
I think your best option is using SwitchNavigator(RouteConfigs, SwitchNavigatorConfig) you can simply create it with createSwitchNavigator as it controls all the switching of navigators used mainly for authentication flow in most react native apps.
With that said, for your case i think its the most suitable way to achieve the desired behavior.
Please be careful if publishing an app using something along the lines of :
this.props.navigation.navigate("WelcomeStack")
as it can be a serious security vulnerability
docs: https://reactnavigation.org/docs/1.x/switch-navigator/
snack example: https://snack.expo.io/#react-navigation/auth-flow
I had a similar problem here how i resolved it:
Added SwitchNavigator like so:
let Navigation = createSwitchNavigator(
{
AuthLoading: AuthLoading,
LoginNavigator: LoginNavigator,
HomeNavTab: tabNavigator,
LoggedInChose: LoggedInChose
},
{
initialRouteName: "AuthLoading"
}
);
The AuthLoading stack is the first to load and decides where the app go next:
import React from "react";
import { connect } from "react-redux";
class AuthLoading extends React.Component {
constructor(props) {
super(props);
this.checkUserSession();
}
checkUserSession = () => {
const userData = this.props.userData;
if (userData) {
const residencesNumber = userData.nbreResidences;
if (residencesNumber == 1) {
this.props.navigation.navigate("HomeNavTab");
} else {
this.props.navigation.navigate("LoggedInChose");
}
} else {
this.props.navigation.navigate("LoginNavigator");
}
};
// Render any loading content that you like here
render() {
return (
null
);
}
}
const mapStateToProps = state => {
return {
userData: state.userData
};
};
export default connect(mapStateToProps)(AuthLoading);
Maybe you get an idea from this.

React Native Navigation and Redux Persist

I am trying to integrate redux-persist with wix react-native-navigation. However, I am unable to find any examples or documentation stating the boilerplate code needed to integrate the both libraries.
I was wondering if anyone would like to share their solution if they have solved this issue ?
First of all, the basic setup should be the similar with or without react-native-navigation as described in the documentation in store.js:
import { persistStore, persistCombineReducers } from 'redux-persist'
import storage from 'redux-persist/es/storage' // default:
localStorage if web, AsyncStorage if react-native
import reducers from './reducers' // where reducers is an object of
reducers
const config = {
key: 'root',
storage,
}
const reducer = persistCombineReducers(config, reducers)
function configureStore () {
// ...
let store = createStore(reducer)
return store
// We'll skip persistStore for now
// let persistor = persistStore(store)
//return { persistor, store }
}
The persistStore call is commented out as we'll do it below. The persistStore method takes a callback in its third argument. The callback is executed after the state is restored/rehydrated. This is nice because this means we can delay starting the screen(s) until the state is rehydrated.
Let's assume you have the following bootstrap code in App.js:
store = configureStore()
registerScreens(store, Provider)
Navigation.startTabBasedApp({
tabs: [{...},]
})
Now we can add persistStore and wrap your bootstrap code in it like this:
store = configureStore()
persistStore(store, null, () => {
registerScreens(store, Provider)
Navigation.startTabBasedApp({
tabs: [{...},]
})
})
Note:
In v4, you pass config instead of null: persistStore(store, config, callback)
In case you're looking to integrate it with react-native-navigation v2, in App.js, make sure you call persistStore() inside the registerAppLaunchedListener() :
import { persistStore } from 'redux-persist';
...
Navigation.events().registerAppLaunchedListener(() => {
persistStore(store, null, () => {
Navigation.registerComponentWithRedux(...);
...
Navigation.setRoot({...})
...
})
})
Adding to his solution you can also use subscribe() to check if your user is still logged in. That way they don't need to sign in again if they completely close the app (for those users with a login system) and since it is only called once the store is persisted, you can start your app after this is checked.
import {Platform, AsyncStorage, AppState} from "react-native"
import {Navigation} from "react-native-navigation"
import {registerScreens} from "./routes"
import {Provider} from "react-redux"
import configureStore from "./stores/reduxStore"
import {Component} from "react"
const storage = configureStore()
registerScreens(Provider, storage.store)
let startapp = screen => {
Navigation.startSingleScreenApp({
screen: {
screen, // unique ID registered with Navigation.registerScreen
navigatorStyle: {
navBarHidden: true,
statusBarHidden: false,
statusBarColor: "white",
statusBarTextColorScheme: "dark"
}, // override the navigator style for the screen, see "Styling the navigator" below (optional)
navigatorButtons: {} // override the nav buttons for the screen, see "Adding buttons to the navigator" below (optional)
},
drawer: {
left: {
screen: "Drawer", // unique ID registered with Navigation.registerScreen
passProps: {} // simple serializable object that will pass as props to all top screens (optional)
}
},
tabsStyle: {
// optional, add this if you want to style the tab bar beyond the defaults
tabBarButtonColor: "#ffff00", // optional, change the color of the tab icons and text (also unselected). On Android, add this to appStyle
tabBarSelectedButtonColor: "#ff9900", // optional, change the color of the selected tab icon and text (only selected). On Android, add this to appStyle
tabBarBackgroundColor: "#551A8B", // optional, change the background color of the tab bar
initialTabIndex: 1 // optional, the default selected bottom tab. Default: 0. On Android, add this to appStyle
},
appStyle: {
orientation: "portrait"
}
})
}
storage.persistor.subscribe(() => {
storage.store.getState().user.logged
? startapp("mainscreen")
: startapp("loginscreen")
})
We actually dont need redux-persist. We can make our own redux-persist with:
redux + store.subscribe(handlechange)
handleChange function will run when ever something changes in our store.
Also Using aync-await(promise) we are not blocking the main execution thread.
So Inside create store add something like:
store.subscribe(async ()=>{
try {
await AsyncStorage.setItem("store", JSON.stringify(store.getState()));
} catch (error) {
// Error
}
})
Then inside App.js(first component to load). use AsyncStorage.getItem('store'). Then update the store before app starts.
localstorage on the web is a synchronous function which blocks the main thread.
AsynsStorage in react-native doesn't blocks the main thread.

React-Navigation: navigate from actions file

I'm new to RN and JS.
I want to navigate once a login action is complete, but I cannot get it to work. I am using firebase.
This is from my actions file. It throws a firebase error:
export const LOGIN_USER_SUCCESS = 'login_user_success';
export const loginUserSuccess = (dispatch, user) => {
dispatch({
type: LOGIN_USER_SUCCESS,
payload: user
});
this.props.navigation.navigate('groupMain'); // this doesnt work
};
I also tried putting it into the Login component - this might be a step in right direction because there is no firebase error, but nothing happens. So it seems that all works, except the user is not navigated to the proper screen. (I removed the 'this doesnt work' line from above)
componentWillReceiveProps(nextProps) {
this.onAuthComplete(nextProps);
}
onAuthComplete(props) {
if (props.user) {
this.props.navigation.navigate('groupMain');
}
}
Later, I also found this in the official docs and tried to implement by using this code in my action, but it threw a firebase error:
"dispatch - Send an action to the router
Use dispatch to send any navigation action to the router. The other navigation functions use dispatch behind the scenes.
Note that if you want to dispatch react-navigation actions you should use the action creators provided in this library.
See Navigation Actions Docs for a full list of available actions.
import { NavigationActions } from 'react-navigation'
const navigateAction = NavigationActions.navigate({
routeName: 'Profile',
params: {},
// navigate can have a nested navigate action that will be run inside the child router
action: NavigationActions.navigate({ routeName: 'SubProfileRoute'})
})
this.props.navigation.dispatch(navigateAction)"
thanks!
I solved a similar problem by creating a global service to handle programmatic navigation:
services/navigator.js
import { NavigationActions } from 'react-navigation';
let navigator;
export function setNavigator(nav) {
navigator = nav;
}
export function navigate(routeName, params) {
if (navigator) {
navigator.dispatch(NavigationActions.navigate({routeName, params}));
}
}
export function goBack() { ... }
export function reset() { ... }
Then, in my top-level component I store the reference to the navigator when it's created:
screens/App.js
import { setNavigator } from '../services/navigator';
const AppNavigator = StackNavigator(...);
class App extends Component {
render() {
return (
<AppNavigator ref={nav => setNavigator(nav)} />
);
}
}
And finally in my action files (or wherever I need to), I simply use the service to dispatch navigation actions:
actions/login.js
import { navigate } from '../services/navigator';
export function loginUserSuccess() {
// use navigate() anywhere you'd normally use this.props.navigation.navigate()
navigate('NextScreen');
}