Infinite Loop With Higher Order React Redux Component - reactjs

I am having some trouble with an infinite loop when using a Higher Order Component to handle authenticated routes. The authentication is very basic, and is just a boolean of true or false in the redux state.
When the app initially starts, it checks to see if there is a user stored in localStorage. If it does not return null, we immediately dispatch a SIGN_IN action.
import React from 'react'
import ReactDOM from 'react-dom'
import { HashRouter } from 'react-router-dom'
import { Provider } from 'react-redux'
import { createStore, applyMiddleware } from 'redux'
import reducers from './reducers'
import reduxThunk from 'redux-thunk'
import { SIGN_IN } from './actions/types'
import logger from 'redux-logger'
const createStoreWithMiddleware = applyMiddleware(reduxThunk, logger)(createStore)
const store = createStoreWithMiddleware(reducers)
import App from './components/App'
const user = localStorage.getItem('user')
if(user) {
store.dispatch({ type: SIGN_IN })
}
ReactDOM.render (
<Provider store={store}>
<HashRouter>
<App />
</HashRouter>
</Provider>,
document.querySelector('.main')
)
This is the higher order component.
import React, { Component } from 'react';
import { connect } from 'react-redux';
export default function(ComposedComponent) {
class RequireAuth extends Component {
componentWillMount = () => {
if(!this.props.auth)
this.props.history.push('/')
}
render() {
return <ComposedComponent {...this.props} />
}
}
function mapStateToProps(state) {
return { auth: state.auth };
}
return connect(mapStateToProps)(RequireAuth);
}
The logic inside of componentWillMount seems to be what is causing the infinite loop, but I really do not know why. It seems to prevent me from going to any other routes, but as you can see in the console output below, it blows up in extraordinary fashion in doing so.
If it helps, here is the component, where I am wrapping my components with the HOC.
export default class App extends Component {
constructor(props) {
super(props)
}
render() {
return (
<div className="app"
style={{
backgroundImage: 'url("./img/base-bg.jpg")',
backgroundSize: 'cover',
backgroundPosition: 'center center',
backgroundRepeat: 'no-repeat'
}}
>
<Switch>
<Route path="/" exact component={RequireAuth(Intro)} />
<Route path="/landing" exact component={RequireAuth(Intro)} />
<Route path="/journey" component={RequireAuth(Exhibit)} />
<Redirect to="/landing" />
</Switch>
<Footer />
</div>
)
}
}
I am using React Router V4 if that helps. And for clarification, contains a login form, and a landing page. If the user is not authenticated, intro shows a login form, and when authed, the landing page should be shown. Perhaps this is where I am messing things up, and shouldn't be nesting my components. I am unsure though.

This boiled down to the hackalicious nesting and how I was wrapping my components with RequireAuth. Instead of having Login and Landing inside of a component called Intro, I scrapped that and changed my route config to this:
<Switch>
<Route path="/" exact component={Login} />
<Route path="/landing" exact component={RequireAuth(Landing)} />
</Switch>
Here is my HOC now, pretty much the same.
export default function(ComposedComponent) {
class RequireAuth extends Component {
componentDidMount = () => {
if(!this.props.auth)
this.redirectToLogin()
}
componentWillUpdate = nextProps => {
if(!nextProps.auth)
this.redirectToLogin()
}
redirectToLogin = () => this.props.history.push('/')
render() {
return <ComposedComponent {...this.props} />
}
}
function mapStateToProps(state) {
return { auth: state.auth };
}
return connect(mapStateToProps)(RequireAuth);
}

Related

Why is withRouter used with connect () breaking my react app?

Good evening everyone,
I have been trying to add withRouter to my react app so it does not break because of the connect function (see code below).
My code is working, but when i add withRouter to the line below, it breaks my app with the following message :
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(App));
Error: Invariant failed: You should not use <withRouter(Connect(App)) /> outside a Router>
i found this topic : Invariant failed: You should not use <Route> outside a <Router> but it's not helping me with me issue when i try to replace with a single Router
Please find below the whole code used :
App.js
import React, {useEffect}from 'react';
import { BrowserRouter } from 'react-router-dom'
import { Route, Redirect} from 'react-router-dom'
import { withRouter } from 'react-router-dom'
import { connect } from 'react-redux'
import * as actions from './store/actions/index'
// Composants
import Layout from './components/hoc/Layout/Layout'
import BudgetHandler from './components/BudgetHandler/BudgetHandler'
import DashBoard from './components/DashBoard/DashBoard'
import Movements from './components/Movements/Movements'
import Home from './components/Home/Home'
import Logout from './components/Logout/Logout'
import classes from './App.module.css'
const App = (props) => {
useEffect(() => {
props.onTryAutoSignup()
},[])
let routes = <React.Fragment>
<Route path="/" exact component={Home} />
<Redirect to="/" />
</React.Fragment>
if(props.isAuthentificated) {
routes = <React.Fragment>
<Route path="/movements" component={Movements} />
<Route path="/dashboard" component={DashBoard} />
<Route path="/logout" component={Logout} />
<Route path="/handler" component={BudgetHandler} />
<Redirect to="/dashboard" />
</React.Fragment>
}
return (
<div className={classes.App}>
<BrowserRouter>
<Layout>
{routes}
</Layout>
</BrowserRouter>
</div>
);
}
const mapStateToProps = state => {
return {
isAuthentificated: state.auth.token !== null
}
}
const mapDispatchToProps = dispatch => {
return {
onTryAutoSignup: () => dispatch(actions.authCheckState())
}
}
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(App));
And this is happening because i am trying to add this function to the useEffect hook to check permanently if the user is auth or not :
in actions/auth.js
export const authCheckState = () => {
return dispatch => {
const token = localStorage.getItem('token')
if(!token) {
dispatch(logout())
} else {
const expirationTime = new Date(localStorage.getItem('expirationDate'))
const userId = localStorage.getItem('userId')
if(expirationTime > new Date()){
dispatch(logout())
} else {
dispatch(finalSignIn(token, userId))
dispatch(checkAuthTimeout(expirationTime.getSeconds() - new Date().getSeconds()))
}
}
}
}
Thank you for your help
Have a good evening
withRouter can only be used in children components of element. In your case can be used with Movements, DashBoard and other childrens. use it while exporting Movements like
export default withRouter(Movements)
on Movements page.

Send request to check role before render specific component

I have a project structure like that:
import React from 'react';
import { render } from 'react-dom';
import { Provider } from 'react-redux';
import {store, persistor} from './helpers';
import { Main } from './main';
import { PersistGate } from 'redux-persist/integration/react';
import { InitIDB } from './helpers/InitIDB';
require('./bootstrap');
InitIDB();
render(
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<Main />
</PersistGate>
</Provider>,
document.getElementById('root')
);
This is entry point, and then the Main component:
import React from 'react';
import { Route, Switch, Router } from 'react-router-dom';
import { connect } from 'react-redux';
import { history } from '../helpers';
import {PrivateRoute} from "../components";
import { ProjectPage } from '../forms/project';
import { ProfilePage } from '../forms/profile';
import { Login } from '../forms/login';
class Main extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<React.Fragment>
<Router history={history}>
<Switch>
<Route exact path="/login" component={Login} />
<PrivateRoute exact path="/com/projects" component={ProjectPage} />
<PrivateRoute exact path="/com/profiles" component={ProfilePage} />
</Switch>
</Router>
</React.Fragment>
);
}
}
function mapState(state) {
const { alert } = state;
return { alert };
}
const actionCreators = {
clearAlerts: function() {}
}
const connectedApp = connect(mapState, actionCreators)(Main);
export { connectedApp as Main };
//PrivateRoute
import React from 'react';
import { Route, Redirect } from 'react-router-dom';
import {LayoutX} from '../forms/layout';
export const PrivateRoute = ({ component: Component, ...rest }) => (
<Route {...rest} render={props => (
sessionStorage.getItem('user')
? <LayoutX><Component {...props} /></LayoutX>
: <Redirect to={{ pathname: '/login', state: { from: props.location } }} />
)} />
)
I want when user access private route, before that the program will send a request to server and it will return a response decide user can be access this page or not. I try to send a request with axios in PrivateRoute but it is async request and I can not get response before render. What should I do?
Your PrivateRoute should block render when waiting for the authentication status from API is returned.
You can take a look at a simple implementation of PrivateRoute I wrote in this codesandbox.

Can we redirect to in reduxSaga

I've a login page with HOC I pass component which must render be after successful login.
Here, if I check for isLoggedIn and redirect in render of sigin-in for then I get error
err: Invariant Violation: Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops
saga.js
try{//call api
//put login success
<Redirect to="/app"/>
}
index.js
const AuthProfile=requireAuth(App);
In reactdom
<Route render={(props)=><AuthProfile {...props}/>} path="/app"/>
import React, { PropTypes } from 'react';
import { connect } from 'react-redux';
import { push } from 'react-router-redux';
import { bindActionCreators } from 'redux';
export default function (ComposedComponent) {
class Authenticate extends React.Component {
componentDidMount() {
console.log("12ra")
this._checkAndRedirect();
}
componentDidUpdate() {
this._checkAndRedirect();
}
_checkAndRedirect() {
const { isLoggedIn, redirect } = this.props;
if (!isLoggedIn) {
redirect();
}
}
render() {
console.log("28ra")
return (
<div>
{ this.props.isLoggedIn ? <ComposedComponent {...this.props} /> : null }
</div>
);
}
}
const mapStateToProps = (state) => {
return {
isLoggedIn:state.signInReducer.isLoggedIn
};
};
const mapDispatchToProps = dispatch => bindActionCreators({
redirect: () => push('/')
}, dispatch)
//Authenticate.propTypes = propTypes
return connect(
mapStateToProps,
mapDispatchToProps
)(Authenticate);
}
Is HOC component correct or I'm missing on something?
Is it good practise to redirect in saga?
Can anyone please lemme know how do I get to app component after success i'm stuck there please help thanks
UPDATE
saga.js
yield put({type:LOGIN_SUCCESS,payload:data})
yield put(push('/app'))
index.js
Ex for Page1,Page2 I need
<AuthRoute
exact
path="/"
component={Page1}
redirectTo="/login"
authenticated={this.props.isLoggegIn}
/>
<AuthRoute
exact
path="/"
component={Page2}
redirectTo="/login"
authenticated={this.props.isLoggegIn}
/>
Following is the way where you can navigate from saga:
saga.js
import { push } from 'react-router-redux';
...
// after API call & validate user
yield put(push('/app'));
index.js
<Route strict exact path='/app' component={App}>
<AuthRoute
exact
path="/"
component={Yourcomponent}
redirectTo="/login"
authenticated={hasAccessToken()}
/>
I am going to tell you the best and the simplest way from which you can redirect in any part of the application(i.e. inside redux and outside as well).
Step: 1
Create history.js file.
import { createBrowserHistory } from 'history';
export default createBrowserHistory();
Step : 2 Import it inside App.js file (or, where you have made routers of your application).
import React from 'react';
import { Router, Route, Switch } from 'react-router-dom';
import * as ROUTES from '../../constants/Routes';
import history from './history';
const App = () => {
return (<Router history={history}>
<div className='App'>
<Switch>
<Route exact path={ROUTES.NOTIFICATIONS} component={Notification} />
<Route exacr path={ROUTES.USER_PROFILE} component={UserProfile} />
<Route exact path={ROUTES.SIGN_IN} component={SignInPage} />
<Route exact path={ROUTES.SIGN_UP} component={SignUpPage} />
<Route path='*' component={SignInPage} />
</Switch>
</div>
</Router>
);
};
export default App;
Step: 3 Inside Saga file just import the history.
import { push } from 'react-router-redux';
import { Auth } from "../service/api/Auth";
import history from '../containers/App/history';
import * as ACTION from '../constants/ActionType';
import { put, takeLatest, call } from "redux-saga/effects";
import { userLoginSuccess, userLoginFailed } from "../actions/auth";
export function* userLogin(loginData) {
const payLoad = loginData.payload;
try {
const loginUserData = yield call(Auth.userLoginApiCall, payLoad);
yield put(userLoginSuccess(TOKEN));
history.push('/posts'); // Redirect to Post Page
} catch (err) {
yield put(push(userLoginFailed()))
}
}
export default function* userLoginRequest() {
yield takeLatest(ACTION.USER_LOGIN_REQUEST, userLogin);
}

React: Trouble implementing Redux with React-router

today i'm trying to implement Redux for the first time on a react-app because it has been a mess to manage state/props, it's pretty good on the redux side so far but when i try to link my store with my app + router i run into errors.
Depending on how i place my router tags 2 things appens:
-not compiling (most of the time because i have outside of the router)
-compiling, rendering but when i try to navigate url changes but not the components that should render.
I did many tries (a lot) so i reverted to when i just linked the store.
Below is my index.js:
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import 'bootstrap/dist/css/bootstrap.css';
import { BrowserRouter } from 'react-router-dom';
import { Provider } from 'react-redux'
import store from './store'
ReactDOM.render(
<Provider store={store}>
<BrowserRouter>
<App />
</BrowserRouter>
</Provider>,
document.getElementById('root')
);
And my App.js (shorthen because it's long and messy):
import React, { Component } from 'react';
import { Route, Switch, Link } from 'react-router-dom';
import { connect } from 'react-redux'
// Components imports
import { Provider } from 'react-redux'
import store from './store'
import { ensureAuth, login, register, updateInputAuth, logout } from './actions/authActions'
class App extends Component {
//states
//methods
render() {
const { pathname } = window.location
const { logged, user, loginError, registerError, inputLogin, inputRegister, successMessage } = this.props
return (
<>
<nav className="navbar navbar-expand-lg navbar-light bg-light" id="navbar">
// My app navbar basically, usses <Link> tags
</nav>
{
!logged ?
<>
<ModalConnect />
<ModalRegister />
</>
: null
}
<>
<Switch>
<Route exact path='/' component={Root}/>
<Route path='/ground' render={(props) => <GroundAnalizer {...props} logged={this.state.logged} />} />
<Route path='/air' component={AirAnalizer} />
<Route path='/simulateur' render={(props) => <Simulateur {...props} logged={logged} log={this.connect} reg={this.register} onInputChange={this.onInputChange} register={this.state.register} login= {this.state.login} errors={this.state.errors} errorsLog={this.state.errorsLog} confirmMsg={this.state.confirmMsg} />} />
<Route path='/calculateur-route' component={CalculateurRoute} />
<Route path='/triangulateur' component={Triangulateur} />
</Switch>
</>
</>
)
}
}
export default connect((store) => {
return{
logged: store.auth.logged,
user: store.auth.user,
loginError: store.auth.loginError,
registerError: store.auth.registerError,
inputLogin: store.auth.inputLogin,
inputRegister: store.auth.inputRegister,
successMessage: store.auth.successMessage,
}
})(App)
So there it is, what am i doing wrong and how i should add my store/routing so it does work ?
Take a look at this document.
You need to import withRouter from react-router-dom and wrap the connect export you have there with a call to withRouter in the components that use React Router navigation.
Thus, your code should be something like:
// Before
export default connect((store) => {
return{
logged: store.auth.logged,
user: store.auth.user,
loginError: store.auth.loginError,
registerError: store.auth.registerError,
inputLogin: store.auth.inputLogin,
inputRegister: store.auth.inputRegister,
successMessage: store.auth.successMessage,
}
})(App)
// After
import { withRouter } from 'react-router-dom';
export default withRouter(connect((store) => {
return{
logged: store.auth.logged,
user: store.auth.user,
loginError: store.auth.loginError,
registerError: store.auth.registerError,
inputLogin: store.auth.inputLogin,
inputRegister: store.auth.inputRegister,
successMessage: store.auth.successMessage,
}
})(App))
This link also has some more information on how this works.

Values is redux store reverting to original state after switching view

I'm having an issue in where by I switch routes, and the value of a property in my store is reverting back to it's original state. I'm using react 16 with react router v4.
I'm also noticing that the entire App component rerenders when I change routes. That seems over the top. It's running mapStateToProps and mapDispatchToProps every time. I'm noticing the state passed into mapStateToProps is always empty too.
I'm very puzzled.
main file
import React from 'react';
import { render } from 'react-dom';
import { connect } from 'react-redux';
//install routing deps
import { BrowserRouter } from 'react-router-dom';
import { Provider } from 'react-redux';
import store from './store'
import App from './components/App';
render(
<Provider store={store}>
<BrowserRouter>
<App/>
</BrowserRouter>
</Provider>,
document.getElementById('root')
);
store
import { createStore, compose, applyMiddleware} from 'redux';
import rootReducer from './reducers/index';
const defaultState = { count: 0 };
const enhancers = compose(
window.devToolsExtension ? window.devToolsExtension() : f => f,
);
const store = createStore(rootReducer, defaultState, enhancers);
//adds hot reload for changes to reducer
if(module.hot){
module.hot.accept('./reducers', ()=>{
const nextRootReducer = require(`./reducers/index`).default;
store.replaceReducer(nextRootReducer);
});
};
export default store;
App component
import React, { Component } from 'react';
import { Route, Switch, Redirect, Link } from 'react-router-dom';
import { BrowserRouter, withRouter } from 'react-router-dom';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as actionCreators from '../actions/actionCreators';
class App extends Component {
constructor() {
super();
};
render() {
return (
<div>
main
<a href='/info'>info</a>
<Switch>
<Route exact path="/" render={() => <h1>kawaii world</h1>} />
<Route exact path="/info" render={() => <h1>v kawaii world ;)</h1>} />
</Switch>
</div>
);
};
};
function mapStateToProps(state) {
return {
count: state.count
};
};
function mapDispatchToProps(dispatch) {
return bindActionCreators(actionCreators, dispatch);
};
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(App));
If you make use of tag a literally you are saying that you are willing to go to another page but in react apps that's not the case. In react you navigate from one component to another.
So to fix your issue you need to do this:
import { BrowserRouter, Link, withRouter } from 'react-router-dom';
class App extends Component {
render() {
return (
<div>
<Link to="/">Main</Link>
<Link to="/info">Info</Link>
<Switch>
<Route exact path="/" render={() => <h1>kawaii world</h1>} />
<Route exact path="/info" render={() => <h1>v kawaii world ;)</h1>} />
</Switch>
</div>
);
};
};

Resources