navigation.navigate does not work on one of useEffect(); - react-native

I am developing a code and always need the user to enter the application to check if there is an update, if there is to send the user to an information screen. But for some reason when I use navigation.navigate ('update') it doesn't work, but console.log ("oi"); above it works. What happens is normal is that last useEffect() executes the navigation.navigate ('Menu'); In the console does not show any kind of error.
Code:
useEffect(() => {
async function verifyVersion() {
await api.post('/version', {
version: 'v1.0'
}).then((response)=>{
console.log("oi");
navigation.navigate('update');
});
}
verifyVersion();
}, []);
useEffect(() => {
async function autoLogon() {
if(await AsyncStorage.getItem("Authorization") != null){
await api.post('/checkToken', null, {
headers: { 'Authorization': 'EST ' + await AsyncStorage.getItem("Authorization") }
}).then((res)=>{
navigation.navigate('Menu');
}).catch(function (error){
if(error.response.data.showIn == "text"){
setShowInfo(true);
if(error.response.data.level == 3){
setColorInfo(false);
}else{
setColorInfo(true);
}
setInfoText(error.response.data.error);
}else{
setshowBox(true);
if(error.response.data.level == 3){
setcolorBox(false);
}else{
setcolorBox(true);
}
setboxText(error.response.data.error);
}
});
}
}
autoLogon();
}, []);
Routes:
import { createAppContainer, createSwitchNavigator } from 'react-navigation';
import Login from './pages/Login';
import read from './pages/read';
import Menu from './pages/Menu';
import Resultado from './pages/Resultado';
import NoConnection from './pages/NoConnection';
import update from './pages/update';
const Routes = createAppContainer(
createSwitchNavigator({
Login,
Menu,
read,
Resultado,
NoConnection,
update
})
);
export default Routes;

Write the navigate function call in setTimeOut for 500ms. it works
fine for me
useEffect(() => {
....
setTimeOut(() => navigation.navigate('Dashboard'), 500);
}, []);

In react-navigation, screen mounting works differently from react component mounting. You need to use a focus listener like this:
React.useEffect(() => {
const unsubscribe = navigation.addListener('focus', () => {
if (!someCondition) navigation.navigate('someScreen');
});
return unsubscribe;
}, [navigation]);
More on the topic can be found here and here

Related

Context API dispatch not called with onEffect while using expo-splash-screen

When I am trying to use the dispatch function recieved with the useContext hook I cannot get the change the content of the data inside the context. It looks like as if the call wasn't even made, when I try to log something inside the conext's reducer it doesn't react. When I try to call it from other components, it works just fine.
Sorry if it's not clean enough, I'm not too used to ask around here, if there's anything else to clarify please tell me, and I'll add the necessary info, I just don't know at the moment what could help.
import { QueryClient, QueryClientProvider } from "react-query";
import LoginPage from "./src/pages/LoginPage";
import { UserDataContext, UserDataProvider } from "./src/contexts/UserData";
import { useState } from "react";
import AsyncStorage from "#react-native-async-storage/async-storage";
import { useContext } from "react";
import * as SplashScreen from "expo-splash-screen";
import { useEffect } from "react";
import { useCallback } from "react";
import { UserData } from "./src/interfaces";
SplashScreen.preventAutoHideAsync();
const queryClient = new QueryClient();
export default function App() {
const [appReady, setAppReady] = useState<boolean>(false);
const { loggedInUser, dispatch } = useContext(UserDataContext);
useEffect(() => {
async function prepare() {
AsyncStorage.getItem("userData")
.then((result) => {
if (result !== null) {
console.log(loggedInUser);
const resultUser: UserData = JSON.parse(result);
dispatch({
type: "SET_LOGGED_IN_USER",
payload: resultUser,
});
new Promise((resolve) => setTimeout(resolve, 2000));
}
})
.catch((e) => console.log(e))
.finally(() => setAppReady(true));
}
if (!appReady) {
prepare();
}
}, []);
const onLayoutRootView = useCallback(async () => {
if (appReady) {
await SplashScreen.hideAsync();
}
}, [appReady]);
if (!appReady) {
return null;
}
return (
<>
<UserDataProvider>
<QueryClientProvider client={queryClient}>
<LoginPage onLayout={onLayoutRootView} />
</QueryClientProvider>
</UserDataProvider>
</>
);
}
I'm thinking I use the context hook too early on, when I check the type of the dispatch function here it says it's [Function dispatch], and where it works it's [Function bound dispatchReducerAction].
I think the problem might come from me trying to call useContext before the contextprovider could render, but even when I put the block with using the dispatch action in the onLayoutRootView part, it didn't work.

Alternative for NavigationActions in react-native v6

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

addlistener focus react native only works on second refresh react-native

so I've been trying to reload the content from asyncStorage in a screen when navigating back from a second screen, but it only refreshes when i navigate forth and back again
here is my code
componentDidMount() {
const {navigation} = this.props
navigation.addListener('focus', () => {
AsyncStorage.getItem('Servers').then((servers) => {
servers = JSON.parse(servers);
if (servers) {
return this.setState({servers:servers, loaded: true})
}
this.setState({servers: [], loaded: true});
});
});
};
Also, i think it should be re-rendering everytime a setState is done, but its not doing it for some reason
this is my code after the changes:
focusHandler(){
AsyncStorage.getItem('Servers').then((servers) => {
servers = JSON.parse(servers);
if (servers.length) {
return this.setState({servers, carregado: true})
}
this.setState({carregado: true});
});
}
componentDidMount() {
const {navigation} = this.props
this.focusHandler();
navigation.addListener('focus', this.focusHandler());
};
it gives the following error:
That's the expected behavior ... cause you've only registered a listener for focus event .... Execute the callback of addListener directly in componentDidMount...
componentDidMount() {
const {navigation} = this.props;
yourFocusHandler();
this.unsubscribe = navigation.addListener('focus', yourFocusHandler);
};
componentWillUnmount() {
this.unsubscribe();
}

React native: useSelector redux is empty

I am new and i want to using react native to create android application so after creating project i installed redux and redux thunk and do every config that redux wants to work .
I create a action file :
export const GETSURVEYOR = 'GETSURVEYOR';
const URL = "http://192.168.1.6:3000/";
export const fetchSurveyor = () => {
return async dispatch => {
const controller = new AbortController();
const timeout = setTimeout(
() => { controller.abort(); },
10000,
);
const response = await fetch(`${URL}GetSurveyorList`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({}),
signal: controller.signal
});
clearTimeout(timeout);
const resData = await response.json();
dispatch({
type: GETSURVEYOR,
surveyorList: resData.SurveyorList
});
}
}
after that i create reducer to handle this data :
import {GETSURVEYOR} from '../actions/surveyor'
const initialState = {
surveyorList: []
}
export default (state = initialState, action) => {
switch (action.type) {
case GETSURVEYOR:
return {
...state,
surveyorList: action.surveyorList
};
Now i am using by useSelector, useDispatch from 'react-redux .
import React, { useState, useEffect, useCallback } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import * as surveyorActions from '../store/actions/surveyor';
export default () => {
const [surveyorCount, setSurveyorCount] = useState(0);
const survayers = useSelector(state => state.surveyor.surveyorList);
const dispatch = useDispatch();
const loadSurvayer = useCallback(async () => {
await dispatch(surveyorActions.fetchSurveyor());
console.log('run use Callback');
console.log('returned :', survayers );
// setSurveyorCount(survayers.length);
}, [dispatch]);
useEffect(() => {
loadSurvayer();
}, [dispatch]);
return [loadSurvayer, surveyorCount];
}
When for first time this paged is rendered , of course that survayers is empty but after fetch data in action and set state to reducer , survayers nut to be an empty.
But i get empty still ? I am sure data is fetched from services but i got empty from survayers ?
LOG Running "RNAuditMngm" with {"rootTag":1}
LOG run use Callback
LOG returned : []
LOG run use Callback
LOG returned : []
if i change my useEffect code to this:
useEffect(() => {
loadSurvayer();
}, [dispatch,survayers]);
I fall to loop !!!! How could i change code without loop?
I think everything works fine, but you're not using the console.log in the right place. When you run the loadSurvayer the survayers is empty. It is empty even the second time because you are not passing it as a dependency in the useEffect hook. And like you said, if you pass it as a dependency, then it causes an infinite loop, and that's right because whenever the survayers change, that function will be called again and so on.
So, here's what you have to do:
Remove the dispatch dependency from your useEffect hook.
Change the console.log's outside of the loadSurvayer function.
Remove the await from the dispatch call because it is synchronous.
Here's how to modify your code to work the right way:
import React, { useState, useEffect, useCallback } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import * as surveyorActions from '../store/actions/surveyor';
export default () => {
const [surveyorCount, setSurveyorCount] = useState(0);
const survayers = useSelector(state => state.surveyor.surveyorList);
const dispatch = useDispatch();
const loadSurvayer = useCallback(async () => {
dispatch(surveyorActions.fetchSurveyor()); // Remove the `await`
console.log('run use Callback');
// setSurveyorCount(survayers.length);
}, [dispatch]);
useEffect(() => {
loadSurvayer();
}, []); // <-- remove the `dispatch` from here.
console.log('returned :', survayers ); // <-- Move the console log here
return [loadSurvayer, surveyorCount];
}
Improvement bonus and suggestion: remove the surveyorCount state variable because you don't actually need it as you can return the count directly.
import React, { useState, useEffect, useCallback } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import * as surveyorActions from '../store/actions/surveyor';
export default () => {
// Remove the `surveyorCount`
//const [surveyorCount, setSurveyorCount] = useState(0);
const survayers = useSelector(state => state.surveyor.surveyorList);
const dispatch = useDispatch();
const loadSurvayer = useCallback(async () => {
dispatch(surveyorActions.fetchSurveyor()); // Remove the `await`
console.log('run use Callback');
// setSurveyorCount(survayers.length);
}, [dispatch]);
useEffect(() => {
loadSurvayer();
}, []); // <-- remove the `dispatch` from here.
console.log('returned :', survayers ); // <-- Move the console log here
//return [loadSurvayer, surveyorCount];
return [loadSurvayer, survayers.length]; // <-- Use `survayers.length` instead of `surveyorCount`
}
In useSelector shouldn't you read surveyerList like this state.surveyorList ?. your state doesn't have any object named surveyor but you are currently reading like state.surveyor.surveyorList

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?