Redirect to login page when not logged in causes state transition error - authentication

I am struggling to figure out how to correctly redirect to a login page when the user is not logged in using React and Redux.
Currently, in the constructor of the component, I check to see if the username is set, and if not, I use the routeActions provided by redux-simple-router to redirect to the login page. However, I get this error:
Warning: setState(...): Cannot update during an existing state transition (such as within `render`). Render methods should be a pure function of props and state.
I understand that setting the state inside of the render function should be avoided. but I am not sure where I should detect and redirect. I have also tried checking the auth state in the componentWillReceiveProps and ComponentWillMount, but no luck.
// WordListContainer.js
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import {routeActions} from 'redux-simple-router';
import WordList from '../components/Words/WordList';
import {addWord, editWord, deleteWord, fetchWords} from '../actions/words';
function mapStateToProps(state) {
return {
auth: state.auth,
words: state.words
};
}
function mapDispatchToProps(dispatch) {
return {
router: bindActionCreators(routeActions, dispatch),
actions: bindActionCreators({
addWord, editWord, deleteWord, fetchWords
}, dispatch)
};
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(WordList);
and
// WordList.js
import React, {Component} from 'react';
import {Link} from 'react-router';
import WordListItem from './WordListItem';
export default class WordList extends Component {
constructor(props) {
super(props);
if(!this.props.auth.username) {
// This redirection causes the error
this.props.router.push('/login');
}
}
render() {
...
}
}
Is there a good place where I can check the state and redirect before even trying to render the component? Perhaps somehow using the Container Object, though I am not quite sure how to do it where I have access to both state and dispatch.

try componentDidUpdate() as this lifecycle method will always be called whenever the state changes.

Related

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

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

Can log updated value but cant render it with mobx flow

I am trying to make a really simple api call without any logic at all.Althoough I get an illegible object in the consoel called 'proxy' at leaset (not expected either) I cant return anything in the render() method and it throws a typeError.
my code:
Store:
import {observable, configure, action,flow, computed, decorate, set, runInAction} from 'mobx';
import {observer, inject} from 'mobx-react'
configure({enforceActions:'observed'})
class GenStore {
verseData = []
state = "pending"
getVerseData = flow(function*() {
this.verseData = []
this.state = "pending"
try {
const response = yield fetch('https://api.quranwbw.com/2/10')
const data = response.json()
this.state = "done"
this.verseData = data
} catch (error) {
this.state = "error"
}
})
}
decorate(GenStore, {state:observable, verseData: observable, getVerseData:action})
export default new GenStore()
Retrieval:
import {observable, configure, action,flow, computed, decorate, set, runInAction} from 'mobx';
import { computedFn } from "mobx-utils"
import {observer, inject} from 'mobx-react'
import React from 'react'
import GenStore from './GenStore'
class Show extends React.Component{
componentDidMount(){
this.props.GenStore.getVerseData()
}
render(){
console.log(this.props.GenStore.verseData)
return <h1>{this.props.GenStore.verseData.words[0].word_arabic}</h1>
}
}
export default inject('GenStore')(observer(Show))
error returned when i try to render:
TypeError: Cannot read property '0' of undefined
Any help would be appreciated.
Thanx in advance.
Oh, and if you have any suggestion as to how to implement this call if you think flow isnt the choice method, please advise me and tell me how i can do it best
Because getVerseData is async function and when component renders for the first time verseData is an empty array (why it is empty array though? It should be empty object) and respectively verseData.words is undefined.
You can do several things to deal with it, for example, check if verseData.words exists and if not show some loader component instead.

Changing state in React native App.js from another component

I'm making authentication in an app, and I'm kind of stuck. I have 2 different navigations. One shows if the user is logged in and another one if not. Basically, a Sign in screen. It's working fine if I change the value manually upon the start. But I can't find a way to change a state when a user signs in, for example. Even though the value in auth module changes, it doesn't update in App.js So how can I update the App.js's state from Sign in screen, for example?
import React, { Component } from 'react';
import { AppRegistry, Platform, StyleSheet, Text, View } from 'react-native';
import DrawerNavigator from './components/DrawerNavigator'
import SignedOutNavigator from './components/SignedOutNavigator'
import auth from './auth'
type Props = {};
export default class App extends Component<Props> {
constructor(props) {
super(props)
this.state = {
isLoggedIn: auth.isLoggedIn
}
}
render() {
return (
(this.state.isLoggedIn) ? <DrawerNavigator /> : <SignedOutNavigator />
);
}
}
AppRegistry.registerComponent('App', () => App)
and my auth module, which is very simple
import { AsyncStorage } from 'react-native';
// try to read from a local file
let api_key
let isLoggedIn = false
function save_user_settings(settings) {
AsyncStorage.mergeItem('user', JSON.stringify(settings), () => {
AsyncStorage.getItem('user', (err, result) => {
isLoggedIn = result.isLoggedIn
api_key = result.api_key
});
isLoggedIn = true
});
}
module.exports.save_user_settings = save_user_settings
module.exports.api_key = api_key
module.exports.isLoggedIn = isLoggedIn
First off, there are loads of ways to approach this problem. Because of this I'm going to try explain to you why what you have now isn't working.
The reason this is happening is because when you assign auth.isLoggedIn to your isLoggedIn state, you are assigning the value once, kind of as a copy. It's not a reference that is stored.
In addition to this, remember, React state is generally only updated with setState(), and that is never being called here, so your state will not update.
The way I would approach this problem without bringing in elements like Redux, which is overkill for this problem by itself, is to look into building an authentication higher order component which handles all the authentication logic and wraps your entire application. From there you can control if you should render the children, or do a redirect.
Auth Component
componentDidMount() {
this._saveUserSettings(settings);
}
_saveUserSettings(settings) {
AsyncStorage.mergeItem('user', JSON.stringify(settings), () => {
AsyncStorage.getItem('user', (err, result) => {
isLoggedIn = result.isLoggedIn
api_key = result.api_key
});
this.setState({isLoggedIn: true});
});
}
render() {
const { isLoggedIn } = this.state;
return isLoggedIn ? this.props.children : null;
}
App.js
render() {
<AuthComponent>
//the rest of authenticated app goes here
</AuthComponent>
}
Here's a really quick, incomplete example. But it should showcase to you how you may want to lay your authentication out. You'll also want to consider error handling and such, however.

Async callback from auth0 lock getting lost if triggered after 10 seconds in a react-native app

I have some odd behavior in my react-native app where the callback trigger is never firing if I don't trigger the callback within about 10 seconds.
I'm programmatically triggering the auth0 react-native-lock login modal in the root component and looking for it's callback. If I submit the auth0 lock within about 10 seconds the callback triggers and it works as intended. However, if I don't submit the login within 10 seconds, the lock screen completes as normal but the callback in the login never gets triggered.
If instead of calling login from the root, and I instead trigger it from a button press in the App component, it works fine and I can take longer than 10 seconds to trigger the callback.
Why does the async callback disappear or not get triggered if I call the login action from the root container?
// root.js
import React, { Component } from 'react';
import { createStore, combineReducers, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import { ApolloProvider } from 'react-apollo';
import App from './App';
import { client } from './client';
import { login } from './actions';
import reducers from './reducers';
reducers.apollo = client.reducer();
const store = createStore(
combineReducers(reducers),
{},
compose(
applyMiddleware(client.middleware()),
applyMiddleware(thunk),
),
);
export default class Root extends Component {
componentDidMount() {
store.dispatch(login());
}
render() {
return (
<ApolloProvider client={client} store={store}>
<App />
</ApolloProvider>
);
}
}
And my action that's losing the callback
// actions.js
export function login() {
return (dispatch) => {
lock.show(null, (err, profile, token) => {
// sometimes this callback never fires if
// the login window is open for more than 10 sec.
// then dispatch authenticated actions
});
};
}
I have met the same problem, just turn off the debugger and try again. Everything works fine.

REDUX: understanding a bit the concept + react native

So, I am working on a pretty straight forward mobile app that has these scenes:
a list of people
person profile
add form
now, what I do, when I first load the LIST scene, I make an API call (I have a list component that I populate once I get results from the API... state.people).
All good here... when I tap on a person he's profile opens, no extra API calls, just passing the person object from state.people array.
All good here as well.
When I open ADD NEW person and send the form I make another API call (I post the information and get the new Object back)...
now the bit that is confusing to me.
What I would like is to update the LIST scene state.people by making another API call (get all again) after I get the OK confirmation from the POST.
and then navigate to Person's profile.
but, I am outside the scope of the LIST scene (I am in ADD NEW form). So, what would be the correct redux logic for this one?
The LIST component is already mounted... how do I communicate to LIST if I am on different scene
all these binding actions to components properties is confusing too... why can't redux act like a global hub that would always be accessible and would always retain it's state (at least on mobile app)
There is really a lack of real app examples... so far I see only very simplified examples that are not very useful on the grand scale to understand the whole flow
the store I have
/**
* STORE
*/
'use strict';
import { createStore, applyMiddleware } from 'redux';
import reducer from './_reducer';
import promiseMiddleware from 'redux-promise-middleware';
import thunkMiddleware from 'redux-thunk';
const store = createStore(reducer, {}, applyMiddleware(
thunkMiddleware,
promiseMiddleware()
));
export default store;
and the actions I have:
import * as constants from '../../constants/constants';
import request from '../../utils/request';
export const getAll = () => ({
type: constants.PEOPLE_FETCH,
payload: request(constants.API_PATH + 'person', {method: 'GET'})
});
export const search = (data, searchTerm) => ({
type: constants.PEOPLE_SEARCH,
payload: _filter(data, searchTerm)
});
export const save = (data) => ({
type: constants.PERSON_SAVE,
payload: request(constants.API_PATH + 'person', {method: 'POST', body: JSON.stringify(data)})
});
This can be an example architecture for your app:
Make a Redux store with list of people.
On initial API call, update the store to contain the list fetched by API call.
Wrap your app inside Provider and pass the store to the Provider.
Use connect and mapStateToProps and mapDispatchToProps to connect the Redux store to React state.
Whenever you update or insert new person, and get the new object, you need to dispatch an action which then goes to the reducer function which finally returns the updated Redux store, and dont worry with the re-rendering as React does the re-rendering itself whenever there is a change in a state.
I'll give a small example of store/actions/reducer, with a react + redux app.
store.js
import { applyMiddleware, compose, createStore } from 'redux'
import reducer from './reducer'
import logger from 'redux-logger'
// TOOD: add middleware
let finalCreateStore = compose(
applyMiddleware(logger())
)(createStore)
export default function configureStore (initialState = { todos: [] }) {
return finalCreateStore(reducer, initialState)
}
actions.js
let actions = {
helloWorld: function(data){
return {
type: 'HELLO_WORLD',
data: data
}
}
};
export default actions
reducer.js // Please read from Redux docs that reducers need to be pure functions
export default function myReducer(state = [], action) {
switch (action.type) {
case 'HELLO_WORLD':
return 'welcome' + data;
default:
return state;
}
}
Component.js (the React App) //In component whenever you receive new object, dispatch an action which will modify the store.
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import actions from '../redux/actions'
class App extends Component {
handleClick() {
store.dispath(action.helloWorld("jimmy")); //this dispatches an action, which goes to the reducer to change the state and adds 'welcome' before 'jimmy'
}
render() {
return (
<div onClick={this.handleClick.bind(this)}>
{store.getState()} //getState function to access store values
</div>
)
}
}
function mapStateToProps(state) {
return state
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(actions, dispatch) //binds all the actions with dispatcher and returns them
}
}
export default connect(mapStateToProps, mapDispatchToProps)(App)
This works like whenever you click the 'div' in the React Component, it calls the function, handleClick(), in which there is an action dispatch. This action then calls the reducer itself to update the store. I know you might get confused that how is store getting updated. Its a bit confusing but for that you need to follow a basic tutorial to explain React+Redux.
Please note this is not a runnable example, just a pseudocode. I recommend you to watch this youtube series to completely understand the redux stores+ react+webpack