I am currently developping an app using react native and i could manage to have a login and sign up page but my problem is the following.
After create user with email and password using firebase method, i would like to update the profile info (displayName) in order to display it in the home page.
But it seems that the page redirect to the home page after sign up without the update done
I have set a listener on my navigator stack to display login/signup pages or Home page regarding the value of user.
the Update is correctly set if i close the app and reload it after the first sign up success.
my code is the following
the main stack code
function onAuthStateChange(user) {
setUser(user);
if (initializing) setInitializing(false);
}
useEffect(() => {
const subscriber = auth().onAuthStateChanged(onAuthStateChange);
return subscriber;
}, [];
if (initializing) {
return <LoadingScreen />
if (!user) {
return (
<NavigationCOntainer>
<AuthStackScreen />
</NavigationCOntainer>
);
}
return (
<NavigationCOntainer>
<DrawerStackScreen />
</NavigationCOntainer>
the code to handle the sign up method :
function handgleRegister() {
auth()
.createUserWithEmailAndPassword(email, password)
.then(userCred => {
return userCred.user.updateProfile({
displayName: name,
});
})
.catch(e => {
...
...
});
}
the code in the Home page, i also set a listener of user.
I also have tried by calling the method : auth().currentUser;
But i got the same problem, the displayName at the first display was set to null and i had to close and reload the app to be able to display it.
function onAuthStateChange(user) {
console.log(user);
}
useEffect(() => {
const subscriber = auth().onAuthStateChanged(onAuthStateChange);
return subscriber;
}, [];
the result of the log shows that displayName is null so i assume the sign up process redirect to the home before updating the value.
Does someone has an idea how i can manage to get the update value on the Home page screen?
Thanks in advance
The user's profile information is gotten from their ID token, which Firebase gets then the user signs in (or is created) and then automatically refreshes every hour. If the user's profile is updated within that hour, the client may be showing outdated profile information until it refreshes the token.
You can force the refresh of a token by:
Signing the user out and in again.
Calling User.reload or User.getIdToken(true).
Both of these cases force the client to refresh the ID token, and thus get the latest profile from the servers.
try this.
function handgleRegister() {
auth()
.createUserWithEmailAndPassword(email, password)
.then(userCred => {
userCred.user.updateProfile({
displayName: name,
}).then(()=>{
setUser(auth().currentUser());
};
})
.catch(e => {
...
...
});
}
here's my signup code. I'm just update the profile with the email address right now since i don't have another input for display name. I'm also using redux toolkit.
import React, { Component, useEffect } from "react";
import { View } from "react-native";
import { Button, Input } from "react-native-elements";
import Icon from "react-native-vector-icons/FontAwesome";
import { Dimensions } from "react-native";
import auth from "#react-native-firebase/auth";
import { useDispatch, useSelector } from "react-redux";
import {
clearLoginInfo,
updateLoginInfo,
signInWithEmailAndPassword,
} from "../login/LoginSlice";
export default function SignupScreen(props) {
const { navigation } = props;
const [username, setUsername] = React.useState("");
const [password, setPassword] = React.useState("");
const [passwordErrorMsg, setPasswordErrorMsg] = React.useState("");
const [usernameErrorMsg, setUsernameErrorMsg] = React.useState("");
const [securedText, setSecuredTtext] = React.useState(true);
const dispatch = useDispatch();
function onAuthStateChanged(user) {
console.log("on user change", user);
}
useEffect(() => {
const subscriber = auth().onAuthStateChanged(onAuthStateChanged);
return subscriber; // unsubscribe on unmount
}, []);
const signIn = (e) => {
auth()
.createUserWithEmailAndPassword(e.username, e.password)
.then((user) => {
console.log("user = ", user);
user.user
.updateProfile({
displayName: e.username,
photoURL: "abc.com",
})
.then(() => {
const currentUser = auth().currentUser;
console.log("current user ", auth().currentUser);
dispatch(
updateLoginInfo({
displayName: currentUser.displayName,
photoURL: currentUser.photoURL,
})
);
});
})
.catch((error) => {
if (error.code === "auth/email-already-in-use") {
setUsernameErrorMsg("That email address is already in use!");
}
if (error.code === "auth/invalid-email") {
setUsernameErrorMsg("That email address is invalid!");
}
if (error.code === "auth/weak-password") {
setPasswordErrorMsg("please choose a stronger password");
}
console.error(error);
});
};
const handlePasswordChange = (val) => {
setPassword(val);
};
const handleUserNameChange = (val) => {
setUsername(val);
};
const doSignin = () => {
signIn({ username, password });
};
return (
<View
style={{
flex: 1,
justifyContent: "center",
alignContent: "center",
alignItems: "center",
}}
>
<Input
placeholder="Email"
errorMessage={usernameErrorMsg}
value={username}
leftIcon={{ type: "font-awesome", name: "envelope" }}
onChangeText={handleUserNameChange}
/>
<Input
placeholder="Password"
errorMessage={passwordErrorMsg}
errorStyle={{ color: "red" }}
value={password}
leftIcon={<Icon name="lock" size={40} color="black" />}
rightIcon={
securedText ? (
<Icon
name="eye"
size={32}
onPress={() => setSecuredTtext(!securedText)}
color="black"
/>
) : (
<Icon
name="eye-slash"
size={32}
onPress={() => setSecuredTtext(!securedText)}
color="black"
/>
)
}
onChangeText={handlePasswordChange}
secureTextEntry={securedText}
/>
<Button
title="Sign up"
onPress={() => doSignin()}
buttonStyle={{ backgroundColor: "#eda621" }}
style={{ width: Dimensions.get("window").width * 0.5 }}
/>
<Button
title="Back to Sign in"
style={{ width: Dimensions.get("window").width * 0.5, marginTop: 10 }}
onPress={() => navigation.navigate("Signin")}
/>
</View>
);
}
listener:
useEffect(()=>{
if (user != null && user.displayName != '') {
navigation.navigate("Home");
},
[user]});
Related
I'm looking to get some help as I've been stuck on this for a while. I have a MERN stack application interacting with the Github API. Users are able to search for Github users and save them to their profile on the app and once they do that they will also start following the user on Github. In the same fashion users are able to delete the users from their profile in the app and that will unfollow the same user on Github. The problem is that when I click on the delete user button, it does not update on the browser unless I refresh the page then I see that the user is deleted from the array of saved users. In the Profile.jsx component I am rendering the list of saved users and fetching the information from the database. In the SavedGithubUsersCard.jsx component I am mapping through the saved users data to render the user's information such as name. In DeleteButton.jsx I am deleting the specific saved user when I click on the button. My question is what am I doing wrong? is the prop change not being fired or is there another issue with UseEffect?
Here is the code:
import React, { useEffect, useContext } from 'react';
import swal from 'sweetalert';
import { AppContext } from '../context/AppContext';
import SavedGithubUsersCard from './SavedGithubUsersCard';
import axios from 'axios';
Profile.jsx
const Profile = () => {
const {
githubUserData, setGithubUserData
} = useContext(AppContext);
useEffect(() => {
axios.get('/api/githubdata').then((res) => {
setGithubUserData(res.data);
})
.catch((err) => {
if (err) {
swal('Error', 'Something went wrong.', 'error');
}
});
}, [setGithubUserData]);
return (
<div className="profile-saved-users">
<div className="githubUserData-cards">
<h1 className="saved-users-header">Saved users</h1>
{!githubUserData || githubUserData.length === 0 ? (
<p className="no-saved-users-text">No saved users yet :(</p>
) : (
<SavedGithubUsersCard githubUserData={githubUserData}/>
)}
</div>
</div>
);
};
export default Profile;
import React from 'react';
import { Card, Button } from 'react-bootstrap';
import { Link } from 'react-router-dom';
import DeleteButton from './DeleteButton';
SavedGithubUsersCard.jsx
const SavedGithubUsersCard = ({githubUserData}) => {
return (
<>
{githubUserData?.map(githubUserData => (
<Card key={githubUserData._id} id="saved-users-card">
<Card.Img
variant="top"
src={githubUserData.avatar_url}
id="saved-users-card-image"
/>
<Card.Body id="saved-users-card-information">
<Card.Title
id="saved-users-name"
style={{ textAlign: 'center' }}
>
{githubUserData.name}
</Card.Title>
<Card.Subtitle
id="saved-users-username"
className="mb-2 text-muted"
style={{ textAlign: 'center' }}
>
{githubUserData.login}
</Card.Subtitle>
<Card.Text id="saved-users-profile-url">
Profile URL: {githubUserData.html_url}
</Card.Text>
<Link
to={{ pathname: "githubUserData.html_url" }}
target="_blank"
>
<Button
id="saved-users-profile-button"
variant="outline-primary"
>
View profile
</Button>
</Link>
<DeleteButton githubUserDataId={githubUserData._id} githubUserDataLogin= {githubUserData.login} />
</Card.Body>
</Card>
))}
</>
);
};
export default SavedGithubUsersCard
DeleteButton.jsx
import React from 'react';
import { Button } from 'react-bootstrap';
import axios from 'axios';
import swal from 'sweetalert';
const DeleteButton = ({ githubUserDataLogin, githubUserDataId }) => {
const handleRemove = async () => {
try {
fetch(`https://api.github.com/user/following/${githubUserDataLogin}`, {
method: 'DELETE',
headers: {
Authorization: `token ${process.env.GITHUB_TOKEN}`
}
});
await axios({
method: 'DELETE',
url: `/api/githubdata/${githubUserDataId}`,
withCredentials: true
});
swal(
'Removed from profile!',
`You are no longer following ${githubUserDataLogin} on Github`,
'success'
);
} catch (err) {
swal('Error', 'Something went wrong.', 'error');
}
};
return (
<Button
variant="outline-danger"
style={{ marginLeft: 20 }}
onClick={handleRemove}
id="saved-users-delete-button"
>
Remove User
</Button>
);
};
export default DeleteButton;
AppContext.jsx
import React, { createContext, useState, useEffect } from 'react';
import axios from 'axios';
const AppContext = createContext();
const AppContextProvider = ({ children }) => {
const [currentUser, setCurrentUser] = useState(null);
const [loading, setLoading] = useState(false);
const [githubUserData, setGithubUserData] = useState([]);
const user = sessionStorage.getItem('user');
useEffect(() => {
// incase user refreshes and context is cleared.
if (user && !currentUser) {
axios
.get(`/api/users/me`, {
withCredentials: true
})
.then(({ data }) => {
setCurrentUser(data);
})
.catch((error) => console.error(error));
}
}, [currentUser, user]);
return (
<AppContext.Provider
value={{ currentUser, setCurrentUser, loading, setLoading, githubUserData, setGithubUserData}}
>
{children}
</AppContext.Provider>
);
};
export { AppContext, AppContextProvider };
Thank you!
From what I see of your code when you delete a user you successfully delete them in the back end but don't also update the local state.
Option 1 - Refetch the users list and update local state
const DeleteButton = ({ githubUserDataLogin, githubUserDataId }) => {
const { setGithubUserData } = useContext(AppContext);
const handleRemove = async () => {
try {
fetch(....);
await axios(....);
swal(....);
const usersDataRes = await axios.get('/api/githubdata');
setGithubUserData(usersDataRes.data);
} catch (err) {
swal('Error', 'Something went wrong.', 'error');
}
};
return (
...
);
};
Option 2 - Attempt to manually synchronize the local state
const DeleteButton = ({ githubUserDataLogin, githubUserDataId }) => {
const { setGithubUserData } = useContext(AppContext);
const handleRemove = async () => {
try {
fetch(....);
await axios(....);
swal(....);
setGithubUserData(users =>
users.filter(user => user._id !== githubUserDataId)
);
} catch (err) {
swal('Error', 'Something went wrong.', 'error');
}
};
return (
...
);
};
I am using React context to get the login function and error which is defined in the context provider file to login in into the firebase database, and use the thrown error in login to display it in the app.
My problem is I didn't get any error when I just use the error thrown from the firebase auth to display the error in the app, but I also added other errors like empty fields to also show an error field in the app.
After doing that I get this too many re-renders error. I think this error is due to using multiple if statements in the function. Can you please suggest a new option or if this is wrong explain please.
Context Provider file
import React from 'react';
import auth from '#react-native-firebase/auth';
export const AuthContext = React.createContext();
export const AuthProvider = ({children}) => {
const [user, setUser] = React.useState(null);
const [error, setError] = React.useState('');
return (
<AuthContext.Provider
value={{
user,
setUser,
error,
login: async (email, pwd) => {
try {
await auth().signInWithEmailAndPassword(email, pwd);
} catch (e) {
setError(e);
}
},
register: async (email, pwd) => {
try {
await auth().createUserWithEmailAndPassword(email, pwd);
} catch (e) {
setError(e);
}
},
logout: async () => {
try {
await auth().signOut();
} catch (e) {
console.log(e);
}
},
}}>
{children}
</AuthContext.Provider>
);
};
App file
import React from 'react';
import {Button, Image, Text, TouchableOpacity, View} from 'react-native';
import styles from '../styles/Styles';
import FormInput from '../components/FormInput';
import FormButton from '../components/FormButton';
import SocialButton from '../components/SocialButton';
import {AuthContext} from '../navigation/AuthProvider';
import ErrorText from '../components/ErrorText';
const LoginScreen = ({navigation}) => {
const [email, setEmail] = React.useState('');
const [password, setPassword] = React.useState('');
const {login, error} = React.useContext(AuthContext);
const [errorForwarded, setErrorForwarded] = React.useState(null);
if (error) {
setErrorForwarded(error);
}
if (!email || !password) {
setErrorForwarded('fields-empty');
}
const renderErrorText = e => {
if (e.code === 'auth/invalid-email') {
return <ErrorText errorText="Email invalid!" />;
}
if (e.code === 'auth/user-not-found') {
return <ErrorText errorText="User not found!" />;
}
if (e.code === 'auth/wrong-password') {
return <ErrorText errorText="Wrong password!" />;
}
if (e === 'fields-empty') {
return <ErrorText errorText="Fields cannot be empty!" />;
}
};
return (
<View style={styles.loginContainer}>
<Image
source={require('../assets/rn-social-logo.png')}
style={styles.loginLogo}
/>
<Text style={styles.loginText}>Sign In</Text>
<FormInput
labelValue={email}
onChangeText={email => setEmail(email)}
placeholderText="Email"
iconType="user"
keyboardType="email-address"
autoCapitalize="none"
autoCorrect={false}
/>
<FormInput
labelValue={password}
onChangeText={pwd => setPassword(pwd)}
placeholderText="Password"
iconType="lock"
secureTextEntry={true}
/>
{error ? renderErrorText(errorForwarded) : null}
<FormButton
buttonTitle="Sign In"
onPress={() => {
{
email && password ? login(email, password) : {};
}
}}
/>
<TouchableOpacity style={styles.loginForgetBtn}>
<Text style={styles.loginNavBtnText}>Forgot Password?</Text>
</TouchableOpacity>
<SocialButton
buttonTitle="Sign In with Facebook"
buttonType="facebook-square"
color="#4867aa"
backgroundColor="#e6eaf4"
/>
<SocialButton
buttonTitle="Sign In with Google"
buttonType="google"
color="#de4d41"
backgroundColor="#f5e7ea"
/>
<TouchableOpacity
style={styles.loginForgetBtn}
onPress={() => navigation.navigate('Signup')}>
<Text style={styles.loginNavBtnText}>
Don't have an account? Create here...
</Text>
</TouchableOpacity>
</View>
);
};
export default LoginScreen;
The reason why you are coming across multiple rerenders have nothing to do with the if condition in your function. By running the functions to set the state directly within the component's function, you are forcing a rerender, after which it starts to run the function from top to bottom, where it again sets the state.
Only react hooks are not reinitialized/run and can be prevented from running on every subsequent rerender, so you will need to use the useEffect() hook with an array of dependencies to watch, so that you don't run into unnecessary state changes on rerenders.
I'm going to include changes for the relevant bits of the code(before the return statement on App component)
const [email, setEmail] = React.useState('');
const [password, setPassword] = React.useState('');
const {login, error} = React.useContext(AuthContext);
const [errorForwarded, setErrorForwarded] = React.useState(null);
React.useEffect(() => {
if (error) {
setErrorForwarded(error);
}
}, [error]) //using an array of dependencies like this ensures that the function only runs on changes in the error state
React.useEffect(() => {
if (!email || !password) {
setErrorForwarded('fields-empty');
}
}, [email, password]) //similarly, only run this hook for changes in email or password
Thank you all, I knew the problem that causes the re-render was that multiple if statements directly in the function component, I just couldn't figure out how to overcome that.
It worked when I use a function to check those if conditions to set those state and call that function in button submit before calling the API. But I think useEffect hook does it much better, Thank you all in clarifying me.
Can I clarify something else, say at first error variable is empty so errorForwarded will be set to empty. Now on the re-render again errorForwarded will be set to empty. So my question is even state change is detected the actual state value hasn't changed. Then why re-render occurrs. Thanks.
I am trying to create a project in that login functionality is good and working properly but when I logged in and refreshed the screen the logout button disappears and the login link will come and then again logout button will come.to understand perfectly watch the video https://drive.google.com/file/d/1UvTPXPvHf4EhcrifxDEfPuPN0ojUV_mN/view?usp=sharing, this is because of
const AuthContext = React.createContext()
//useauth will return the AuthContext
export const useAuth = () => {
return useContext(AuthContext)
}
export const Authprovider = ({ children }) => {
var name
auth.onAuthStateChanged((user) => {
name = user
})
const [currentuser, setcurrentuser] = useState(name)
const [load, setload] = useState(true)
function signup(email, password) {
return auth.createUserWithEmailAndPassword(email, password)
}
function login(email, password) {
return auth.signInWithEmailAndPassword(email, password)
}
useEffect(() => {
const unsubscribe = auth.onAuthStateChanged((user) => {
setcurrentuser(user)
setload(false)
})
return unsubscribe
}, [])
const value = {
currentuser,
signup,
login,
load,
}
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>
}
I wrapped the AuthProvider component around the app component so that I can use the values like current user .
in Header component where login link, logout button is there
const { currentuser, load } = useAuth()
const logout = () => {
try {
auth.signOut().then(() => {
console.log('logged out')
})
} catch {
alert('logout is not possible')
}
}
//some code
{currentuser ? (
<button onClick={logout}>Logout</button>
) : (
<Link to='/login'>Login</Link>
)}
if there is a current user then the logout button will appear otherwise login link will appear but when refreshing there is some problem I tried many ways now I am out of ideas. "Even I refresh the page when logged in the logout button should not disappear" can you tell me how to do this?
to understan watch the video in the link
That's because you're not using load state try this:
//some code
{ load ? <div>loading</div>
: currentuser ? (
<button onClick={logout}>Logout</button>
) : (
<Link to='/login'>Login</Link>
)}
I have a react login page with username and password manually inputted,
I want the submit button to automatically click upon page load so it will automatically log in users.
Here is my code.
export const LoginForm = () => {
const history = useHistory();
const classes = useStyles();
const email = 'email#email.com';
const password = 'mypassword';
const [authStatus, setStatus] = useState(null);
const [isSpinnerHidden, setSpinner] = useState(true);
const { session } = useContext(SessionContext);
const recaptchaRef = useRef();
const _onSubmit = async (e) => {
e.preventDefault();
setSpinner(false);
const authResult = await session.signIn(email, password);
setSpinner(true);
console.log(session);
authResult ? setStatus(authResult) : history.push('/dashboard');
};
return (
<form onSubmit={_onSubmit}>
<Typography
align='center'
color='primary'
variant="h4"
style={{ marginBottom: '20px' }}
>
Sign In
</Typography>
{authStatus ?
<Alert
severity="error"
>
{authStatus}
</Alert>
:
null
}
<TextField
type='email'
label="Email"
/>
<TextField
type='password'
label="Password"
/>
<Link
onClick={() => history.push('/restore')}
className={classes.restore}>
Forgot password?
</Link>
<MuiButton
text='Sign In'
type='submit'
value='login'
isSpinnerHidden={isSpinnerHidden}
className={classes.button}
/>
</form>
);
};
what can I add to this code or change that will make it click on the sign-in button automatically and login the user.
In order to automatically click on the submit button, you need to use an onLoad
convert your _onSubmit to a windows onload, something like this
window.onload = async (e) => {
e.preventDefault();
setSpinner(false);
console.log('hello');
const authResult = await session.signIn(email, password);
setSpinner(true);
console.log(session);
authResult ? setStatus(authResult) : history.push('/dashboard');
}
I don't think clicking the button automatically is the best experience you can have here.
The better way of doing that here would be to conditionally choose the route based on authentication state before you even render the login screen. Something like this:
if (isAuthenticated){
history.push('/dashboard);
} else {
history.push('/login');
}
If for whatever reason you NEED to do it your way, just call your signin function when the components mounts with useEffect
import React, {useEffect} from 'react';
export const LoginForm = () => {
...
useEffect(() => {
if(isAuthenticated){ // You'll need to get the auth state from somewhere here
history.push('/dashboard')
}
},[])
return (
...
);
};
I was working on an old app using react navigation version 4 the app contains a register and login in page obviously and then the content of the app.
recently I started remaking the content of the app using react navigation version 5 in order to use the shared element animation and the bottom tab navigator and it was fairly simple.
but I struggled with converting the login part to version 5 since the app structure is somewhat complicated and I am somewhat new to react navigation version 5.
i will leave a figure of the app structure bellow a long with samples of the code used.
App.js :
import { setNavigator } from "./app/navigationRef";
const articleListFlow = createStackNavigator({
Main: MainScreen, // screen with diffrent articles categories
ResultsShow: ResultShowScreen, // article details screen
});
const loginFlow = createStackNavigator({
Signup: SignupScreen,
Signin: SigninScreen,
});
loginFlow.navigationOptions = () => {
return {
headerShown: false,
};
};
articleListFlow.navigationOptions = {
title: "News Feed",
tabBarIcon: ({ tintColor }) => (
<View>
<Icon style={[{ color: tintColor }]} size={25} name={"ios-cart"} />
</View>
),
activeColor: "#ffffff",
inactiveColor: "#ebaabd",
barStyle: { backgroundColor: "#d13560" },
};
const switchNavigator = createSwitchNavigator({
ResolveAuth: ResolveAuthScreen,
MainloginFlow: createSwitchNavigator({
//WelcomeScreen: WeclomeScreen,
loginFlow: loginFlow,
}),
mainFlow: createMaterialBottomTabNavigator(
{
articleListFlow: articleListFlow,
ArticleSave: ArticleSaveScreen, // we dont need this one
Account: AccountScreen,
},
{
activeColor: "#ffffff",
inactiveColor: "#bda1f7",
barStyle: { backgroundColor: "#6948f4" },
}
),
});
const App = createAppContainer(switchNavigator);
export default () => {
return (
<AuthProvider>
<App
ref={(navigator) => {
setNavigator(navigator);
}}
/>
</AuthProvider>
);
};
NavigationRef.js :
import { NavigationActions } from "react-navigation";
let navigator;
export const setNavigator = (nav) => {
navigator = nav;
};
export const navigate = (routeName, params) => {
navigator.dispatch(
NavigationActions.navigate({
routeName,
params,
})
);
};
// routename is the name of the routes singin singup accountscreen
// params information we want to pass to the screen we want to show
AuthContext.js
import { AsyncStorage } from "react-native";
import createDataContext from "./createDataContext";
import userAPI from "../api/user";
// using navigate to access the navigator and redirect the user
import { navigate } from "../navigationRef";
// AUTHENTICATION REDUCERS
const authReducer = (state, action) => {
switch (action.type) {
case "add_error": {
return {
...state,
errorMessage: action.payload,
};
}
case "clear_error_message": {
return {
...state,
errorMessage: "",
};
}
case "signin": {
return {
errorMessage: "",
token: action.payload,
};
}
default:
return state;
}
};
// CLEARING ERROR MESSAGES WHEN SWITCHING SIGNIN-SIGNUP
const clearErrorMessage = (dispatch) => () => {
dispatch({ type: "clear_error_message" });
};
// AUTOMATIC SIGNIN ONLY USING TOKENS ON USER DEVICE
const tryLocalSignin = (dispatch) => async () => {
const token = await AsyncStorage.getItem("token");
if (token) {
// if token exists
dispatch({ type: "signin", payload: token });
navigate("Main");
} else {
// if token doesnt exist
navigate("WelcomeScreen");
}
};
// SIGNUP
const signup = (dispatch) => async ({ email, password }) => {
try {
const response = await userAPI.post("/signup", { email, password });
await AsyncStorage.setItem("token", response.data.token);
dispatch({ type: "signin", payload: response.data.token });
// making use of the navigate component to access navigation
// and redirect the user
navigate("Main");
} catch (err) {
dispatch({
type: "add_error",
payload: "Something went wrong with sign up",
});
}
};
// SIGNIN
const signin = (dispatch) => async ({ email, password }) => {
try {
const response = await userAPI.post("/signin", { email, password });
await AsyncStorage.setItem("token", response.data.token);
// using signin since the logic is the same
dispatch({ type: "signin", payload: response.data.token });
// making use of the navigate component to access navigation
// and redirect the user
navigate("Main");
} catch (err) {
console.log(err);
dispatch({
type: "add_error",
payload: "Something went wrong with sign in",
});
}
};
// SIGNOUT
const signout = (dispatch) => async () => {
// removing the token makes identification not work again
await AsyncStorage.removeItem("token");
dispatch({ type: "signout" });
navigate("loginFlow");
};
// CREATING CONTEXT AND PROVIDER OBJECTS FOR AUTHENTICATION
export const { Provider, Context } = createDataContext(
authReducer,
{
signin,
signup,
signout,
clearErrorMessage,
tryLocalSignin,
},
{
token: null,
errorMessage: "",
}
);
createDataContext.js
import React, { useReducer } from "react";
export default (reducer, actions, defaultValue) => {
const Context = React.createContext();
const Provider = ({ children }) => {
const [state, dispatch] = useReducer(reducer, defaultValue);
const boundActions = {};
for (let action in actions) {
// for every action in the actions, call it with dispatch
boundActions[action] = actions[action](dispatch);
}
return (
<Context.Provider value={{ state, ...boundActions }}>
{children}
</Context.Provider>
);
};
return { Context, Provider };
};
My appologies for the long code and thank you in advance for anyone who can help.
There are several things that you need to consider when moving from V4 to V5 it involves some changes and also you can consider using features like the hooks.
The first change will be removing the Switch Navigator and conditionally render the navigator in its place. This will be done in your App.js. As you already have a reducer based implementation you can use the state values to take this decision.
The next change will be the creation of stacks, in V4 you create the navigation by passing the screen, now everything is a component and you pass the screens as children.
The option are also sent as props to either the navigator or the screen itself.
The usage of navigation ref is still possible but you can also use hooks like usenavigation inside components and for your authentication flow you wont be using this as you conditionally render the navigators.
I have made a simplified version based on your code.
App.js
const AuthStack = createStackNavigator();
const AppTabs = createMaterialBottomTabNavigator();
const ArticleStack = createStackNavigator();
const Articles = () => {
return (
<ArticleStack.Navigator>
<AppTabs.Screen name="ArticlesList" component={ArticleList} />
<AppTabs.Screen name="ArticlesDetails" component={ArticleDetail} />
</ArticleStack.Navigator>
);
};
export default function App() {
const [state, dispatch] = React.useReducer(authReducer, {
isLoading: true,
token: null,
errorMessage: '',
});
React.useEffect(() => {
const bootstrapAsync = async () => {
const userToken = await AsyncStorage.getItem('userToken');
dispatch({ type: 'RESTORE_TOKEN', token: userToken });
};
bootstrapAsync();
}, []);
const authContext = React.useMemo(
() => ({
signIn: async (data) => {
dispatch({ type: 'SIGN_IN', token: 'dummy-auth-token' });
},
signOut: () => dispatch({ type: 'SIGN_OUT' }),
signUp: async (data) => {
dispatch({ type: 'SIGN_IN', token: 'dummy-auth-token' });
},
}),
[]
);
return (
<AuthContext.Provider value={authContext}>
<NavigationContainer>
{state.token === null ? (
<AuthStack.Navigator headerMode="none">
{state.isLoading ? (
<AuthStack.Screen name="Welcome" component={WelcomeScreen} />
) : (
<>
<AuthStack.Screen name="SignIn" component={SignInScreen} />
<AuthStack.Screen name="SignUp" component={SingUpScreen} />
</>
)}
</AuthStack.Navigator>
) : (
<AppTabs.Navigator
activeColor="#f0edf6"
inactiveColor="#3e2465"
barStyle={{ backgroundColor: '#694fad' }}>
<AppTabs.Screen
name="Articles"
component={Articles}
options={{
tabBarLabel: 'Home',
tabBarIcon: ({ color, size }) => (
<MaterialCommunityIcons
name="home"
color={color}
size={size}
/>
),
}}
/>
<AppTabs.Screen name="Search" component={SearchScreen} />
<AppTabs.Screen name="Save" component={SaveScreen} />
<AppTabs.Screen name="Account" component={AccountScreen} />
</AppTabs.Navigator>
)}
</NavigationContainer>
</AuthContext.Provider>
);
}
Auth Context
const AuthContext = React.createContext();
export default AuthContext;
Auth Reducer
export const authReducer = (state, action) => {
switch (action.type) {
case 'RESTORE_TOKEN':
return {
...state,
token: action.token,
isLoading: false,
};
case 'SIGN_IN': {
return {
errorMessage: '',
token: action.payload,
};
}
case 'SIGN_OUT': {
return {
errorMessage: '',
token: null,
};
}
default:
return state;
}
};
As you can see the flow will be showing the welcome screen till the token is loaded from async storage and then based on that show the tabs or the login screen. Also the parameters are passed as props. I've moved the actions to app.js but it can be separated as well.
You can see a fully running sample here
https://snack.expo.io/#guruparan/navigation-sample-3
Hope this helps, Feel free to ask if there are any questions.
As per your diagram, I have tried to create Navigation
const WelcomeStack = createStackNavigator();
const Tab = createBottomTabNavigator();
const ArticleStack = createStackNavigator();
const MainStack = createStackNavigator();
function Welcome(){
return(
<WelcomeStack.Navigator>
<WelcomeStack.screen name='SignIn' component={SignIn}/>
<WelcomeStack.screen name='SignUp' component={SignUp}/>
</WelcomeStack.Navigator>
)
}
function Article(){
return(
<ArticleStack.Navigator>
<ArticleStack.Screen name='ArtcileList' name={ArticleList}/>
<ArticleStack.Screen name='ArticleDetail' name={ArtcileDetail}/>
</ArticleStack.Navigator>
)
}
function TabNav(){
<Tab.Navigator>
<Tab.Screen name='Article' component={Article}/>
<Tab.Screen name='Search' component={Search}/>
<Tab.Screen name='Save' component={Save}/>
<Tab.Screen name='Account' component={Account}/>
</Tab.Navigator>
}
function App(){
return(
<NavigationContainer>
<MainStack.Navigator>
{this.state.isLogin ?
<MainStack.Screen name='Tab' component={TabNav}/>
:
<MainStack.Screen name = 'WelcomeStack' component={Welcome}/>
}
</MainStack.Navigator>
</NavigationContainer>
)
}
In react navigation 5, their is no switch navigator so you have to go with stack navigation + ternary operator.
This is just an idea as per your diagram. You can make it better after some R&D.