Checking if user signed in before react native - javascript

In my react native app I save the user information securely on the key chain, so that after they have logged in once, I save the information and then the next time the user comes, the information is already there and so the user won't need to log in.
The issue is that I do the check in componentDidMount, and then if the user has never logged in before or logged out in their last visit I redirect them to the loginScreen like so:
componentDidMount() {
//Need to check if they've signed in before by checking the USER_INFO.
SecureStore.getItemAsync("USER_INFO").then(response => {
//Have they signed in before?
if (response !== undefined) {
//yes.
//continue with app stuff.
}
else {
//Not logged in before need to go to login.
const resetAction = NavigationActions.reset({
index: 0,
actions: [
NavigationActions.navigate({ routeName: 'Login', params: this.props.navigation.state.params }),
]
});
this.props.navigation.dispatch(resetAction);
}
});
}
The problem is that I get a warning that 'Warning: Can only update a mounted or mounting component. This usually means you called setState, replaceState, or forceUpdate on an unmounted component. This is a no-op.'. Which makes sense because I am redirecting before the screen has rendered, but then the question is, where should I perform these checks?
Thanks

I see that you are using react-navigation. I have done the same thing that you are trying to accomplish but in a different way.
To simplify I have three screens in a navigator
// Navigator.js
export const RootNavigator = StackNavigator({
Splash: { screen: SplashView },
Login: { screen: LoginView },
RootDrawer: { screen: RootDrawer }
}, {
headerMode: 'none',
initialRouteName: 'Splash'
});
And then in my SplashView (which is my starting point) I authenticate the user in its constructor. And while it is authenticating the user, the SplashView is simply rendering a Text that says "Splash Screen" but could obviously be anything.
constructor(props) {
super(props);
this.authenticateSession();
}
authenticateSession() {
const { navigation } = this.props;
dispatch(storeGetAccount())
.then(() => {
navigation.dispatch(navigateToAccountView(true));
})
.catch(() => {
navigation.dispatch(navigateToLoginView());
});
}
The functions navigateToAccountView() and navigateToLoginView() are just so I can use them at other places but the navigateToLoginView() looks like this
export function navigateToLoginView() {
return NavigationActions.reset({
index: 0,
key: null,
actions: [
NavigationActions.navigate({ routeName: 'Login' })
]
});
}

Usually and as far as I know, the best way to handle this kind of checks is by wrapping your component by some HOC(High Order Component) Doing your logic there, and depending if the user passes the checks you can throw a redirection to login page or load the user data and keep forward rendering your component.
This is a good practice so you can create a withAuth() HOC that will wrap the components or the parts of your app that can only be accessed by authenticated users. And you will have a component that is highly reusable.
So you will export your "protected component" like this:
export default withAuth(myComponent)
performing the logic in the withAuth HOC instead of in you component.

Related

setState() on root container doesn't keep routes of react-navigation (V3)

I have a root Component (App) which renders a nested navigation as shown in the code.
Inside of app, I have the user object (stored in state) which is used by all child objects. It contains information about the groups you're in.
export default class App extends React.Component {
constructor(props) {
super(props)
this.state = {
user: null
}
}
render() {
if (!this.state.user) {
// from the login component, I make an API call to my backend.
// If the user logs in, I do app.setState({user: loggedInUser}) from inside the LoginComponent
return <LoginComponent app={this} />
}
return createAppContainer(createSwitchNavigator({
MainApp: {
screen: createBottomTabNavigator({
StartPage: {
screen: ({ navigation }) => {
return <StartPage navigation={navigation} app={this} />
}
},
Groups: {
screen: createStackNavigator({
ListGroups: {
// When clicking on a Group from the GroupsPage, I execute:
// this.props.navigation.navigate('GroupDetail', { group, app })
// (from inside the GrousPage screen)
screen: ({ navigation }) => <GroupsPage navigation={navigation} app={this} />
},
GroupDetail: {
// Inside the GroupDetail, you can leave or join a group.
// If you do that, I'm making an API call and get the new user object.
// Then, I do this.props.navigation.getParam('app').setState({user: newUser})
// "app" was passed from ListGroups
screen: GroupDetail
}
})
}
})
}
}))
}
}
Now, when I'd like to set the updated user (from GroupDetail via app.setState({user: newUser})), the navigation doesn't keep its route and goes back to StartPage.
However, what I wanted instead is to re-render GroupDetail with the new user.
How would I be able to keep the navigation state? Or do you think I have a design flaw and any idea on fixing it? Thanks!
(Yes, I'd like to keep the user object only inside of App and pass it down. Main reason is to only have few API calls)
you should not create a new navigator on authentication but instead add a route to your navigator, which will be your default route and this route will take care of your login.
It should be passed a callback to set the user like this:
setUser = user => this.setState({user});
return <AuthPage setUser={this.setUser} /> // You also don't have to pass navigation. It will be passed automatically.
Inside your AuthPage you can set the user with
this.props.setUser(newUser);
and navigate to your desired route with the navigation prop or just go back to the previous route:
this.props.navigation.navigate('App');
Or this.props.navigation.goBack();
To keep user logged in you should check if the user is logged in in your Auth route and react accordingly:
componentDidMount() {
const user = getUser(); // This depends on how you persist the user
this.props.navigation.navigate(user ? 'App' : 'Auth');
}
Hope this helps.
According to docs, you have to persist the user navigation state.
read more here.

Wait for VueX value to load, before loading component

When a user tries to directly navigate load a component url, an http call is made in my vuex actions, which will define a value in my state once it resolves.
I don't want to load my component until the http call is resolved, and the state value is defined.
For Example, in my component
export default {
computed: {
...mapState({
// ** this value needs to load before component mounted() runs **
asyncListValues: state => state.asyncListValues
})
},
mounted () {
// ** I need asyncListValues to be defined before this runs **
this.asyncListValues.forEach((val) => {
// do stuff
});
}
}
How can I make my component wait for asyncListValues to load, before loading my component?
One way to do it is to store state values.
For example, if your store relies on single API, you would do something like this. However, for multiple APIs, it's a good idea to store each api load state individually, or using a dedicated object for each API.
There are usualy 4 states that you can have, which I prefer to have in a globally accessible module:
// enums.js
export default {
INIT: 0,
LOADING: 1,
ERROR: 2,
LOADED: 3
};
Then, you can have the variable stored in the vuex state, where the apiState is initialized with INIT. you can also initialize the array with [], but that shouldn't be necessary.
import ENUM from "#/enums";
// store.js
export default new Vuex.Store({
state: {
apiState: ENUM.INIT,
accounts: [],
// ...other state
},
mutations: {
updateAccounts (state, accounts) {
state.accounts = accounts;
state.apiState = ENUM.LOADED;
},
setApiState (state, apiState) {
state.apiState = apiState;
},
},
actions: {
loadAccounts ({commit) {
commit('setApiState', ENUM.LOADING);
someFetchInterface()
.then(data=>commit('updateAccounts', data))
.catch(err=>commit('setApiState', ENUM.ERROR))
}
}
});
Then, by adding some computed variables, you can toggle which component is shown. The benefit of using state is that you can easily identify the Error state, and show a loading animation when state is not ready.
<template>
<ChildComponent v-if="apiStateLoaded"/>
<Loader v-if="apiStateLoading"/>
<Error v-if="apiStateError"/>
</template>
<script>
import ENUM from "#/enums";
export default {
computed: {
...mapState({
apiState: state=> state.apiState
}),
apiStateLoaded() {
return this.apiState === ENUM.LOADED;
},
apiStateLoading() {
return this.apiState === ENUM.LOADING || this.apiState === ENUM.INIT;
},
apiStateError() {
return this.apiState === ENUM.ERROR;
},
})
}
</script>
aside... I use this pattern to manage my applications as a state machine. While this example utilizes vuex, it can be adapted to use in a component, using Vue.observable (vue2.6+) or ref (vue3).
Alternatively, if you just initialize your asyncListValues in the store with an empty array [], you can avoid errors that expect an array.
Since you mentioned vue-router in your question, you can use beforeRouteEnter which is made to defer the rendering of a component.
For example, if you have a route called "photo":
import Photo from "../page/Photo.vue";
new VueRouter({
mode: "history",
routes: [
{ name: "home", path: "/", component: Home },
{ name: "photo", path: "/photo", component: Photo }
]
});
You can use the beforeRouteEnter like this:
<template>
<div>
Photo rendered here
</div>
</template>
<script>
export default {
beforeRouteEnter: async function(to, from, next) {
try {
await this.$store.dispatch("longRuningHttpCall");
next();
} catch(exception) {
next(exception);
}
}
}
</script>
What it does is, waiting for the action to finish, updating your state like you want, and then the call to next() will tell the router to continue the process (rendering the component inside the <router-view></router-view>).
Tell me if you need an ES6-less example (if you do not use this syntax for example).
You can check the official documentation of beforeRouteEnter on this page, you will also discover you can also put it at the route level using beforeEnter.
One approach would be to split your component into two different components. Your new parent component could handle fetching the data and rendering the child component once the data is ready.
ParentComponent.vue
<template>
<child-component v-if="asyncListValues && asyncListValues.length" :asyncListValues="asyncListValues"/>
<div v-else>Placeholder</div>
</template>
<script>
export default {
computed: {
...mapState({
asyncListValues: state => state.asyncListValues
})
}
}
</script>
ChildComponent.vue
export default {
props: ["asyncListValues"],
mounted () {
this.asyncListValues.forEach((val) => {
// do stuff
});
}
}
Simple way for me:
...
watch: {
vuexvalue(newVal) {
if (newVal == 'XXXX')
this.loadData()
}
}
},
computed: {
...mapGetters(['vuexvalue'])
}
Building on some of the other answers, if you're using Router, you can solve the problem by only calling RouterView when the state has been loaded.
Start with #daniel's approach of setting a stateLoaded flag when the state has been loaded. I'll just keep it simple here with a two-state flag, but you can elaborate as you like:
const store = createStore({
state () {
return {
mysettings: {}, // whatever state you need
stateLoaded: false,
}
},
mutations: {
set_state (state, new_settings) {
state.settings = new_settings;
state.stateLoaded = true;
},
}
}
Then, in app.vue you'll have something like this:
<div class="content">
<RouterView/>
</div>
Change this to:
<div class="content">
<RouterView v-if="this.$store.state.stateLoaded"/>
</div>
The v-if won't even attempt to do anything with RouterView until the (reactive) stateLoaded flag goes true. Therefore, anything you're rendering with the Router won't get called, and so there won't be any undefined state variables in it when it does get loaded.
You can of course build on this with a v-else to perhaps show a "Loading..." screen or something, just in case the state loading takes longer than expected. Using #daniel's multi-state flag, you could even report if there was a problem loading the state, and offer a Retry button or something.

What is the best practices for redirecting users in React applications?

I have seen much more cases related to redirecting users in react applications and every case was just a different approach to the solution. There are some cases, where redirecting has occurred in actions like this`
export const someAction = (values, history) => async dispatch => {
const res = await someAsyncOperation(props);
history.push('/home');
dispatch(someAction);
}
In this example history object (form react-router) is being passed in react component. For me, this approach is not acceptable.
There is also a special Redirect from react-router.
After then I have already searched many articles and couldn't just find anything.
So in your opinion, what's the best practice for redirecting and where to handle such kind of processes ?
In React, you usually achieve redirects in the componentDidUpdate of your components.
In the case of async actions, you will check a flag stored in the Redux store, generally a boolean like isFetching, isCreating, isUpdating, etc…, which will be modified by the actions.
Simple example:
class EditUser extends Component {
compondentDidUpdate(prevProps) {
if (prevProps.isUpdating && !this.props.isUpdating) {
// ↑ this means that the async call is done.
history.push('/users')
}
}
updateUser() {
const modifiedUser = // ...
this.props.updateUser(modifiedUser)
// ↑ will change state.users.isUpdating from false to true during the async call,
// then from true to false once the async call is done.
}
render() {
// ...
<button onClick={this.updateUser}>Update</button>
// ...
}
}
const mapStateToProps = (state, props) => ({
userToEdit: state.users.items.find(user => user.id === props.userId)
isUpdating: state.users.isUpdating,
})
const mapActionsToProps = {
updateUser: usersActions.updateUser,
}
export default connect(mapStateToProps, mapActionsToProps)(EditUser)
The next step is usually to add another flag in your Redux store to track if the async calls are successful or not (e.g. state.users.APIError, in which you can keep the error returned by the API). Then you achieve the redirect only if there are no errors.
We mostly redirect a user due to when user logged in or when sign out. For example here's basic requireAuth HOC component to check if user is logged in or not and redirect him to another place.
import React, { Component } from 'react';
import { connect } from 'react-redux';
export default ChildComponent => {
class ComposedComponent extends Component {
componentDidMount() {
this.shouldNavigateAway();
}
componentDidUpdate() {
this.shouldNavigateAway();
}
shouldNavigateAway() {
if (!this.props.auth) {
this.props.history.push('/');
}
}
render() {
return <ChildComponent {...this.props} />;
}
}
function mapStateToProps(state) {
return { auth: state.auth.authenticated };
}
return connect(mapStateToProps)(ComposedComponent);
};
There are two position to check if user is logged in
When the first time that component mount - in componentDidMount()
When user try to sign in , log in or sign out - in componentDidUpdate()
Also in your code sample, history.push is in an action creator. Action creators belongs to redux side. Keep redux & react separate.

redux reset props after redirect

I'm using redux and had problem with this code in my component
componentWillReceiveProps(nextProps) {
if(nextProps.data.deleted_ad){
this.props.history.replace('/')
}
}
The problem with this code is that when I nagivate from '/' to 'ad', this will bring me back to '/' again.
Here's my reducer look like
case 'DELETE_AD_PENDING':
return {
...state,
loading: true
}
case 'DELETE_AD_FULFILLED':
return {
...state,
deleted_ad: action.payload.data.success === true ? true : false
}
case 'DELETE_AD_REJECTED':
return {
...state,
loading: false,
error: action.payload.response.data.error
}
It's partially working, but not after user click into a new ad because nextProps.data.deleted_ad is still true after the redirect.
Try react-router-redux (react-router 4 example) ! It works beautifully. You don't have to worry about such props as is_deleted in your component for redirection - you can keep them but don't use them for redirection (I would use them only for rendering)
You put ALL HISTORY into redux and then act an async action for redirection after a successful action. Like this:
In your component (create form component example with a form on submit):
/* components/Create.js */
import {addMeeting} from "../../store/actions/meetingActions";
class Create extends Component {
....
onSubmit(e) {
e.preventDefault();
this.props.dispatch(addMeeting(this.state.meeting));
}
....
}
In your actions declarations (using thunk middleware):
/* meetingActions.js */
import axios from "axios/index";
import {push} from 'react-router-redux';
export function addMeeting(meeting) {
return function (dispatch) {
dispatch({type: 'ADD_MEETING_STARTED'});
axios.post('/api/meeting', meeting)
.then((res) => {
dispatch({type: 'ADD_MEETING_FULFILLED', payload:res.data})
dispatch(push('/')); // <--- HERE
})
.catch((err) => {
dispatch({type: 'ADD_MEETING_ERROR', payload:err.response.data.errors})
});
}
}
You can see a full example of how it works in my repository of basic MERN CRUD stack SPA right here. The code that is might interest you most is in client/store directory.
This solution has an advantage that you will never associate redirection with a component but with an action instead. You can add a second argument to action addMeeting(meeting, redirectTo) to specify where router should go after action.
redux-logger shows this:
Hope it helped!

React update parent component when child changes the Context

I have four React components:
An overall parent (App or similar)
A Header child, called by App
A Profile page
A LogoutLink, called by Profile
I am implementing a User authentication/login system using Auth0. When the user logs in (via a Login button in the Header), App changes the Context of the User object to include all the data retrieved from Auth0. This user data is then accessible to any part of the system which requires it.
When logged in, the UI automatically updates (using Context changes) so that the Header is now showing "Hey there {name}" rather than "Login" as before. This is also a link leading to the Profile page/component (using React Router's <Link to="/profile></Link> element).
On the Profile page there is a LogoutLink. When clicked, this logs the user out, and returns to the home page. It should also update the UI automatically to change the message in the Header back from "Hey there {name}" to "Login". This is done by Context changes again. However, this feature doesn't actually work - the user is successfully logged out, but to see the change described just above, the user needs to refresh the whole page. this.context.user is not being updated and sent back to Profile and Header. I know this is because of Redux and it's one-way data flow (i.e data can only go downwards, not up), but I need to find a way around it.
Here is the basic code I have:
LogoutLink.js
export default class LogoutLink extends React.Component {
constructor(props) {
super(props);
this.onClick = this.onClick.bind(this);
this.state = {
user: null,
};
}
static propTypes = {
value: React.PropTypes.string,
}
static contextTypes = {
user: React.PropTypes.object,
} // get context so this.context is available to get initial user data (before logout)
static childContextTypes = {
user: React.PropTypes.object,
}
getChildContext() {
return {user: this.state.user}
} // these two are for updating context
componentWillMount() {
this.setState({ user: this.context.user });
} // set the internal LogoutLink state
onClick() {
this.setState({ user: null }); // set the internal user state to null following logout
}
renderLogoutLink() {
const {value} = this.props;
const {user} = this.state;
if (user != null) {
return <Link to="/profile" onClick={this.onClick}>{value}</Link>
} else {
return <span>You're already logged out!</span>
}
}
render() {
return <span>{this.renderLogoutLink()}</span>
}
}
Header.js:
export default class Header extends React.Component { // eslint-disable-line react/prefer-stateless-function
constructor(props) {
super(props);
this.showLock = this.showLock.bind(this); // lock is the Auth0 module responsible for Login, also passed down by context
}
static contextTypes = {
user: React.PropTypes.object,
lock: React.PropTypes.object,
}
showLock() {
const {lock} = this.context;
lock.show();
}
shouldComponentUpdate(nextProps, nextState, nextContext) {
if (this.context.user == null && nextContext.user != null) {
return true;
} else if (this.context.user != null && nextContext.user == null) {
return true;
}
return false;
} // after the LogoutLink is clicked, this still returns false
renderLoginButton() {
const {user} = this.context;
if (user) {
const name = user.nickname;
return <Link to="/profile">Hey there {name}!</Link>
} else {
return <button onClick={this.showLock}>Login</button>
}
}
render() {
return (
<header>
{this.renderLoginButton()}
</header>
);
}
}
I am following the official React docs about Context and updating it, found here: https://facebook.github.io/react/docs/context.html
As I said I know why this is not working: data can only be sent one way. But I need to find a way to make this work, and to be honest I think I've been staring at this too long and am now out of options and my brain is a bit frazzled.
Found a solution to this issue. In my App.js code, which is setting the initial context for the User item, I have added a method onto the User to set the User to null, which then trickles down through the app. In the Logout link it calls this method. Here is the code:
In App.js:
profile.logout = () => {
this.setState({ profile: null });
}
the getChildContext() method then sets the user context from this state change:
getChildContext() {
return {
user: this.state.profile
};
}
In LogoutLink.js:
onClick() {
const {user} = this.context;
user.logout();
}
The React "context" feature is useful, but also complicated and quirky, which is why there's still a number of cautions around using it. In particular, I believe that components that return false from shouldComponentUpdate will cause lower components to not update if the context contents change.
My advice would be to use context as little as possible, and only for data that does not change. Put changing data, such as the current user, into your Redux xtate.

Categories

Resources