Alternative for NavigationActions in react-native v6 - react-native

I have problem, namely the navigation in this code doesn't work:
import AsyncStorage from "#react-native-async-storage/async-storage";
import createDataContext from "./createDataContext";
import trackerApi from "../api/tracker";
import { navigate } from "./navigationRef";
const authReducer = (state, action) => {
switch (action.type) {
case "add_error":
return { ...state, errorMessage: action.payload };
case "signin":
return { errorMessage: "", token: action.payload };
case "clear_error_message":
return { ...state, errorMessage: "" };
case "signout":
return { token: null, errorMessage: "" };
default:
return state;
}
};
const signup = (dispatch) => async ({ email, username, birth, gender, password }) => {
try {
const response = await trackerApi.post("/signup", { email, username, birth, gender, password });
await AsyncStorage.setItem("token", response.data.token);
dispatch({ type: "signin", payload: response.data.token });
console.log(response.data.token);
navigate("DrawerScreen");
} catch (err) {
dispatch({
type: "add_error",
payload: "Something went wrong with sign up",
});
}
};
export const { Provider, Context } = createDataContext(
authReducer,
{ signin, signout, signup, clearErrorMessage, tryLocalSignin },
{ token: null, errorMessage: "" }
);
"signup" function successfully sends my data to database in mongodb. But after this
The next file is created to help my navigation works. But "NavigationActions" was used in ReactNative v4. I need to change my code to work with RN v6. The following code is pasted below:
import { NavigationActions } from 'react-navigation';
let navigator;
export const setNavigator = nav => {
navigator = nav;
};
export const navigate = (routeName, params) => {
navigator.dispatch(
NavigationActions.navigate({
routeName,
params
})
);
};
Both files are referenced by each other.
To sum up I've tried the solution to use navigation.navigate("MyScreen"), but it doesnt work in signup function. The question is how to change the second file to work with RN6 or how to navigate successfully in this function without the second file?

First you have to import useNavigation
Like this:
import { useNavigation } from "#react-navigation/core";
Then you have to use it and save it in a variable like:
const navigation = useNavigation();
Now use onPress when press on that button to navigate:
onPress={() => navigation.navigate('MyScreen')};
This will navigate to the the other Screen.
Make sure you install every library you use in your project using npm or yarn.

You can get access to the root navigation object through a ref and pass it to the RootNavigation which we will later use to navigate.
// App.js
import { NavigationContainer } from '#react-navigation/native';
import { navigationRef } from './RootNavigation';
export default function App() {
return (
<NavigationContainer ref={navigationRef}>{/* ... */}
</NavigationContainer>
);
}
In the next step, we define RootNavigation, which is a simple module with functions that dispatch user-defined navigation actions.
// RootNavigation.js
import {createNavigationContainerRef} from '#react-navigation/native';
import {StackActions} from '#react-navigation/native';
export const navigationRef = createNavigationContainerRef();
// for navigate
export function navigate(name, params) {
if (navigationRef.isReady()) {
navigationRef.navigate(name, params);
}
}
// for replace
export function navigateReplace(name, param) {
if (navigationRef.isReady()) {
navigationRef.dispatch(
StackActions.replace(name, {
param,
}),
);
}
}
// any js module
import * as RootNavigation from './path/to/RootNavigation.js';
then you can navigate like this
RootNavigation.navigateReplace('ChatScreen', { userName: 'Lucy' });
or
RootNavigation.navigate('ChatScreen', { userName: 'Lucy' });
for more details, you can read the documentation
Navigating without the navigation prop

Related

How to use useNavigation() Hook in Redux actions React Native

I want to navigate user from my redux actions. For example when they
click on login, they navigate from action
.
Two ways i have tried.
1.pass navigation prop from component to action. (it works fine.)
2. use useNavigation() hook in redux actions. (it is not working. (Hooks can only be called inside of the body of a function component)).
Here is my code
action.js
export const registerUser = (data) => {
const navigation = useNavigation()
return async dispatch => {
dispatch(authLoading());
try {
const res = await axios.post(
`${BASE_URL}/mobilesignup`,
data,
{
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
},
);
console.log(res);
dispatch(registerSuccess(res));
navigation.navigate('dashboard')
} catch (err) {
dispatch(authFailed(err));
}
};
};
This code is not working
Error (Hooks can only be called inside of the body of a function
component)
Can anybody help me how can i use useNavigation() in redux actions ?
Thanks
You will have to use the Navigation ref which is there for purposes like calling from the reducer
The idea is to create a navigation.js and set the reference of navigation container and use it.
Code would be like below. (A sample from documentation)
//App.js
import { NavigationContainer } from '#react-navigation/native';
import { navigationRef } from './RootNavigation';
export default function App() {
return (
<NavigationContainer ref={navigationRef}>{/* ... */}</NavigationContainer>
);
}
// RootNavigation.js
import * as React from 'react';
export const navigationRef = React.createRef();
export function navigate(name, params) {
navigationRef.current?.navigate(name, params);
}
You can simply import the navigation js anywhere and call navigate
Documentation
https://reactnavigation.org/docs/navigating-without-navigation-prop/#handling-initialization
According to the documentation of react-navigation v6.x
Define your rootNavigation module as followed:
// RootNavigation.ts
import { createNavigationContainerRef } from "#react-navigation/native";
const navigationRef = createNavigationContainerRef();
export class RootNavigation {
static navigate(name: string, params: any = {}) {
if (navigationRef.isReady()) {
navigationRef.navigate(name, params);
}
}
static get ref():any {
return navigationRef;
}
}
Pass the reference to NavigationContainer located at the root of your App.
// App.js
import { NavigationContainer } from '#react-navigation/native';
import { navigationRef } from './RootNavigation';
export default function App() {
return (
<NavigationContainer ref={RootNavigation.ref}>{/* ... */}</NavigationContainer>
);
}
Then simply use it at an action creator
// any js module
// ...
RootNavigation.navigate('ChatScreen', { userName: 'Lucy' });

Handling Errors from Redux API Call as a Toast

So I'm trying to figure out the best way to display a Toast error and success function when the API call fires from redux.
My line of thinking: Create action for the API call. If successful, then I want the screen to change to the home screen. If it fails, then display the message in a Toast.
Here's what some of my actions look like:
export function getTokenAPI(username, password) {
return async function action(dispatch) {
try {
dispatch({ type: t.AUTH_GET_TOKEN });
dispatch(setLoading(true));
const { data } = await API.authGetToken(username, password);
const { success } = data;
if (success) {
const { access_token, refresh_token } = data;
dispatch(setAccessToken(access_token));
dispatch(setRefreshToken(refresh_token));
await dispatch(setLoading(false));
} else if (!success) {
const { errorMessage } = data;
throw Error(errorMessage);
}
} catch (e) {
dispatch(setError(e.message));
dispatch(setLoading(false));
}
};
}
The setError action sets the error key to true and sets the errorMessage. Here's what my screen looks like:
import React from 'react';
import { Container, View, Toast } from 'native-base';
import styles from './styles';
import { connect } from 'react-redux';
import { authActions } from '_ducks/auth';
const LoginScreen = props => {
const { getToken, navigation } = props;
const { navigate } = navigation;
const navigateToHome = () => navigate('Home');
const handleLogin = async () => {
const { error, errorMessage } = props;
await getToken('sample', 'pass123');
if (error) {
Toast.show({
text: errorMessage,
buttonText: 'kay',
});
} else {
navigateToHome();
}
};
return (
<Container>
<View style={styles.container}>
<LoginButton onPress={handleLogin} />
</View>
</Container>
);
};
const mapDispatchToProps = dispatch => ({
getToken: () => dispatch(authActions.getTokenAPI()),
});
const mapStateToProps = state => ({
isLoading: state.authReducer.isLoading,
error: state.authReducer.error,
errorMessage: state.authReducer.errorMessage,
});
export default connect(
mapStateToProps,
mapDispatchToProps,
)(LoginScreen);
So if there's an error, then display the toast. If it's successful, navigate to the home screen. Essentially, error will not be true quick enough to make the check within handleLogin work appropriately.
Any recommendations on the pattern or process? Should I be using a useEffect hook here?

State is not updating after reducer is updating the state

I'm trying to get data from App.js for network connection availability. I'm getting data from App.js to action and reducer but the reducer is not updating the state for my component. The console log in the reducer is working but I'm not able to get data in the mapStateToProps of myComponent.
My App.js file contains this code.
import React, { Component } from 'react';
import { Provider } from 'react-redux';
import { NetInfo } from 'react-native';
import { createStore, applyMiddleware } from 'redux';
import ReduxThunk from 'redux-thunk';
import reducers from './src/reducers';
import Router from './src/Router';
import { internetConnectionChanged } from './src/actions/';
class App extends Component {
componentWillMount() {
NetInfo.isConnected.addEventListener('connectionChange', this.handleConnectionChange);
}
componentWillUnmount() {
NetInfo.isConnected.removeEventListener('connectionChange', this.handleConnectionChange);
}
handleConnectionChange = (isConnected) => {
NetInfo.isConnected.fetch().done(
(isConnecteds) => {
const store = createStore(reducers, {}, applyMiddleware(ReduxThunk));
store.dispatch(internetConnectionChanged(isConnecteds));
});
};
render() {
const store = createStore(reducers, {}, applyMiddleware(ReduxThunk));
return (
<Provider store={store}>
<Router />
</Provider>
);
}
}
export default App;
My code in action file is
import { CONNECTION_CHANGE } from '../actions/types';
export const internetConnectionChanged = (isConnected) => {
return {
type: CONNECTION_CHANGE,
payload: isConnected
};
};
That is exported via the index.js of actions file
through export * from './AppActions';
Code for the reducer is
import { CONNECTION_CHANGE } from '../actions/types';
const INITIAL_STATE = { isConnected: false };
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case CONNECTION_CHANGE:
console.log(action.payload);
return { ...state, isConnected: action.payload };
default:
return state;
}
};
Under my component, this is the code to get the info is
const mapStateToProps = ({ auth, app }) => {
const { email, password, error, loading } = auth;
const { isConnected } = app;
return { email, password, error, loading, isConnected };
};
export default connect(mapStateToProps, {
emailChanged,
passwordChanged,
loginUser,
forgotPasswordAction,
})(LoginForm);
Create store outside the App class. This might be causing the store to always have initial reducer values. Just paste the below line before Class App extends Component line
const store = createStore(reducers, {}, applyMiddleware(ReduxThunk));
Also remove the same above line of code from the following function
handleConnectionChange = (isConnected) => {
NetInfo.isConnected.fetch().done(
(isConnecteds) => {
const store = createStore(reducers, {}, applyMiddleware(ReduxThunk)); //remove this line
store.dispatch(internetConnectionChanged(isConnecteds));
});
};

Trying to integrate Redux into React Navigation

I'm trying to integrate Redux, into an existing React Native application who use React Navigation.
The dependencies in package.json file are:
"react": "^16.0.0",
"react-native": "^0.51.0",
"react-native-smart-splash-screen": "^2.3.5",
"react-navigation": "^1.0.0-rc.2",
"react-navigation-redux-helpers": "^1.0.0",
My code are:
./App.js
import React, { Component } from "react"
import { AppRegistry, StyleSheet, View } from "react-native"
import { Provider } from "react-redux"
import { createStore } from "redux"
import SplashScreen from "react-native-smart-splash-screen"
import AppReducer from "./reducers/AppReducer"
import AppWithNavigationState from "./navigators/AppNavigator"
class App extends Component {
store = createStore(AppReducer);
componentWillMount() {
SplashScreen.close({
animationType: SplashScreen.animationType.scale,
duration: 850,
delay: 500,
});
}
render() {
return (
<Provider store={store}>
<AppWithNavigationState />
</Provider>
)
}
}
AppRegistry.registerComponent("App", () => App)
export default App
./navigators/AppNavigator.js
import { addNavigationHelpers, StackNavigator } from "react-navigation"
import { connect } from "react-redux"
import StackLoading from "../screens/app/StackLoading"
import StackAuth from "../screens/auth/StackAuth"
export const AppNavigator = StackNavigator({
Login: { screen: StackAuth },
Main: { screen: StackLoading },
},
{
headerMode: 'screen',
header: null,
title: 'MyApp',
initialRouteName: 'Login',
})
const AppWithNavigationState = ({ dispatch, nav }) => (
<AppNavigator
navigation={addNavigationHelpers({ dispatch, state: nav })}
/>
);
const mapStateToProps = state => ({
nav: state.nav,
})
export default connect(mapStateToProps)(AppWithNavigationState)
./reducers/AppReducer.js
import { combineReducers } from 'redux';
import NavReducer from './NavReducer';
const AppReducer = combineReducers({
nav: NavReducer,
});
export default AppReducer;
./reducers/AppReducer.js
import { combineReducers } from 'redux';
import { NavigationActions } from 'react-navigation';
import { AppNavigator } from '../navigators/AppNavigator';
const router = AppNavigator.router;
const mainNavAction = AppNavigator.router.getActionForPathAndParams('Main')
const mainNavState = AppNavigator.router.getStateForAction(mainNavAction);
const loginNavAction = AppNavigator.router.getActionForPathAndParams('Login')
const initialNavState = AppNavigator.router.getStateForAction(loginNavAction, mainNavState)
function nav(state = initialNavState, action) {
let nextState;
switch (action.type) {
case 'Login':
nextState = AppNavigator.router.getStateForAction(
NavigationActions.back(),
state
);
break;
case 'Logout':
nextState = AppNavigator.router.getStateForAction(
NavigationActions.navigate({ routeName: 'Login' }),
state
);
break;
default:
nextState = AppNavigator.router.getStateForAction(action, state);
break;
}
// Simply return the original `state` if `nextState` is null or undefined.
return nextState || state;
}
const initialAuthState = { isLoggedIn: false };
function auth(state = initialAuthState, action) {
switch (action.type) {
case 'Login':
return { ...state, isLoggedIn: true };
case 'Logout':
return { ...state, isLoggedIn: false };
default:
return state;
}
}
const AppReducer = combineReducers({
nav,
auth,
});
export default AppReducer;
I have used various approaches following as many guides. The error that I continue to have is this:
ReactNativeJS: undefined is not an object (evaluating
'state.routes[childIndex]')
ReactNativeJS: Module AppRegistry is not a
registered callable module (calling runApplication)
Please help me :\
PrimaryNavigator is my top level navigator.
I am using some helper functions that disables pushing the same component multiple times to the stack which is a common problem in react-navigation.
My helper functions respectively ;
function hasProp(obj, prop) {
return Object.prototype.hasOwnProperty.call(obj, prop);
}
// Gets the current route name
function getCurrentRouteName(nav) {
if (!hasProp(nav, 'index') || !hasProp(nav, 'routes')) return nav.routeName;
return getCurrentRouteName(nav.routes[nav.index]);
}
function getActionRouteName(action) {
const hasNestedAction = Boolean(
hasProp(action, 'action') && hasProp(action, 'type') && typeof action.action !== 'undefined',
);
const nestedActionWillNavigate = Boolean(hasNestedAction && action.action.type === NavigationActions.NAVIGATE);
if (hasNestedAction && nestedActionWillNavigate) {
return getActionRouteName(action.action);
}
return action.routeName;
}
And then setting the nav reducer :
const initialState = PrimaryNavigator.router.getStateForAction(
NavigationActions.navigate({ routeName: 'StartingScreen' })
);
const navReducer = (state = initialState, action) => {
const { type } = action;
if (type === NavigationActions.NAVIGATE) {
// Return current state if no routes have changed
if (getActionRouteName(action) === getCurrentRouteName(state)) {
return state;
}
}
// Else return new navigation state or the current state
return PrimaryNavigator.router.getStateForAction(action, state) || state;
}
Finally, you can combine navReducer inside your combineReducers function.
Please let me know if my answer does not help your case
AppRegistry.registerComponent("App", () => App) should happen in index.ios.js or index.android.js
your index.ios.js file should look like
import App from './src/App';
import { AppRegistry } from 'react-native';
AppRegistry.registerComponent('your_app_name', () => App);

React Native Redux - initialize default state from asyncstorage

i'm developing an android application whereby my screen has to depends on whether the user is logged in or not. The login data is stored inside the AsyncStorage.
By the time when the apps start, it should get the data from AsyncStorage and make it as the default state. How can i achieve that ?
Below is my redux structure
index.android.js
import {
AppRegistry,
} from 'react-native';
import Root from './src/scripts/Root.js';
AppRegistry.registerComponent('reduxReactNavigation', () => Root);
Root.js
import React, { Component } from "react";
import { Text, AsyncStorage } from "react-native";
import { Provider, connect } from "react-redux";
import { addNavigationHelpers } from 'react-navigation';
import getStore from "./store";
import { AppNavigator } from './routers';
const navReducer = (state, action) => {
const newState = AppNavigator.router.getStateForAction(action, state);
return newState || state;
};
const mapStateToProps = (state) => ({
nav: state.nav
});
const user = {};
class App extends Component {
constructor(){
super();
}
render() {
return (
<AppNavigator
navigation={addNavigationHelpers({
dispatch: this.props.dispatch,
state: this.props.nav
})}
/>
);
}
}
const AppWithNavigationState = connect(mapStateToProps)(App);
const store = getStore(navReducer);
export default function Root() {
return (
<Provider store={store}>
<AppWithNavigationState />
</Provider>
);
}
user reducers
import {
REGISTER_USER,
UPDATE_USER,
LOGIN_USER
} from '../../actions/actionTypes';
import { handleActions } from 'redux-actions';
**// here is the default state, i would like to get from asyncstorage**
const defaultState = {
isLoggedIn: false,
user:{},
};
export const user = handleActions({
REGISTER_USER: {
next(state, action){
return { ...state, user: action.payload, isLoggedIn: true }
}
},
LOGIN_USER: {
next(state, action){
return { ...state, user: action.payload, isLoggedIn: true }
}
},
}, defaultState);
You can use the component lifecycle method componentWillMount to fetch the data from AsyncStorage and when the data arrive you can change the state.