0

Good Morning, I am trying to develop a React app to communicate with a backend through REST APIs.

The app architecture uses also React Router and implements authentication feature with a JWT token given by the server and stored in the SessionStorage.

Basically:

  • a user connects to the app
  • if the user is not logged in, he is redirected to login page
  • when logged in it is redirect to homepage with path /entsorga/home, which should display the component with path /entsorga and the component with path /entsorga/home (if a understood the working of React Router well)

By the starting of the app, a user is correctly redirected to login page and can authenticate correctly, afte the authentication the page url is changed in the correct way but the components does not render and the subsequent error is shown.

While I am totally new to React, I am facing the error:

Error: 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.

I know the error comes from the fact that a function continously calls setState, I already tried to eliminate one by one the calls to the function but this does not solve the issue, so it shouldn't be about one of my functions.

is there something I maybe miss from React Router which interacts with components lifecycle hooks and make this error happen?

index.tsx page:

ReactDOM.render(
    <React.StrictMode>
        <BrowserRouter>
            <Route exact path='/'>
                {authenticationService.isUserAuthenticated() ? <Redirect to='/entsorgafin' /> : <Login />}
            </Route>
            <Route path='/entsorgafin'>
                {authenticationService.isUserAuthenticated() ? <Redirect to='/entsorga/home' /> : <Redirect to='/' />}
            </Route>
            <Switch>
                <Route path='/entsorga' component={Header} />
                <Route path='/entsorga/home' component={Homepage} />
            </Switch>
        </BrowserRouter>
    </React.StrictMode>,
    document.getElementById('root')
);

Header.tsx component:

class Header
    extends Component<any, any>
{
    //costruttore
    constructor(props: any)
    {
        //props management
        super(props);
        
        //state management
        this.state = {
            anniAttivita: [],
            nomeProprietaGrafici: [],
            loggedIn: authenticationService.isUserAuthenticated
        }
        
        //methods binding
        this.getAnniAttivita = this.getAnniAttivita.bind(this);
        this.setProprietaCruscotto = this.setProprietaCruscotto.bind(this);
        this.logout = this.logout.bind(this);
    }
    
    //component lifecycle hook --> creation
    componentDidMount()
    {
        this.getAnniAttivita();
        this.setProprietaCruscotto();
    }
    
    //metodo per recuperare gli anni di attività dell'impianto
    getAnniAttivita()
    {
        utilityService.recuperaAnniAttivita().then(
            r =>
            {
                this.setState({anniAttivita: r.data})
            }
        );
    }
    
    //metodo per settare le proprietà disponibili all'interno del cruscotto
    setProprietaCruscotto()
    {
        let proprietaGrafici = [
            {
                beName: "caloPesoGiorno",
                feName: "Calo Peso Giornaliero"
            },
            {
                beName: "caloPeso",
                feName: "Calo Peso"
            },
            {
                beName: "temperaturaMateriale",
                feName: "Temperatura Materiale"
            },
            {
                beName: "temperaturaReattore",
                feName: "Temperatura Reattore"
            },
            {
                beName: "rhInterna",
                feName: "RH Interna"
            },
            {
                beName: "deltaP",
                feName: "Delta P"
            },
            {
                beName: "tempoLatenza55",
                feName: "Tempo Latenza 55"
            },
            {
                beName: "ph",
                feName: "PH"
            },
            {
                beName: "rh",
                feName: "RH"
            },
            {
                beName: "germinazione",
                feName: "Germinazione"
            },
            {
                beName: "inerti_litoidi",
                feName: "Inerti Litoidi"
            },
            {
                beName: "plastiche_vetri_metalli",
                feName: "Plastiche, Vetri, Metalli"
            }
        ]
        
        this.setState({nomeProprietaGrafici: proprietaGrafici});
    }
    
    //effettua il logout
    logout()
    {
        authenticationService.logout();
        this.setState({loggedIn: false});
    }
    
    //metodo che effettua il render del componente
    render()
    {
        if (this.state.loggedIn)
        {
            return (
                <Redirect to='/' />
            );
        }
        
        return (
            <nav className="navbar navbar-light sticky-top navbar-expand-xl py-0 pl-0">
                <Link className="navbar-brand py-0" to={"/entsorgafin/home"}>
                    <img src={logo} className="img-fluid d-inline-block align-top" alt="EntsorgaFin logo" />
                </Link>
                <button className="navbar-toggler" type="button" data-toggle="collapse"
                        data-target="#navbarSupportedContent"
                        aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
                    <span className="navbar-toggler-icon" />
                </button>
                
                <div className="collapse navbar-collapse" id="navbarSupportedContent">
                    <ul className="navbar-nav mr-auto">
                        <li className="nav-item dropdown with-right-border">
                            <Link className="nav-link dropdown-toggle text-dark" to="#"
                                  id="tracciabilitaLottiNavbarDropdown"
                                  role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
                                DOCUMENTO TRACCIABILIT&Aacute; LOTTI
                            </Link>
                            <div className="dropdown-menu" aria-labelledby="tracciabilitaLottiNavbarDropdown">
                                {
                                    //creazione di un link per ogni anno di attività
                                    this.state.anniAttivita.map(
                                        (anno: number) =>
                                        {
                                            return (
                                                <Fragment>
                                                    <Link key={anno} className="dropdown-item text-center"
                                                          to={"/entsorgafin/tracciabilitaLotti"}>{anno}</Link>
                                                    <div className="dropdown-divider custom-dropdown-divider" />
                                                </Fragment>
                                            );
                                        }
                                    )
                                }
                            </div>
                        </li>
                        <li className="nav-item dropdown with-right-border">
                            <Link className="nav-link dropdown-toggle text-dark" to="#"
                                  id="performanceImpiantoNavbarDropdown" role="button" data-toggle="dropdown"
                                  aria-haspopup="true" aria-expanded="false">
                                PERFORMANCE D'IMPIANTO
                            </Link>
                            <div className="dropdown-menu" aria-labelledby="performanceImpiantoNavbarDropdown">
                                {
                                    //creazione di un link per ogni anno di attività
                                    this.state.anniAttivita.map(
                                        (anno: number) =>
                                        {
                                            return (
                                                <Fragment>
                                                    <Link key={anno} className="dropdown-item text-center"
                                                          to={"/entsorgafin/performanceImpianto"}>{anno}</Link>
                                                    <div className="dropdown-divider custom-dropdown-divider" />
                                                </Fragment>
                                            );
                                        }
                                    )
                                }
                            </div>
                        </li>
                        <li className="nav-item dropdown">
                            <Link className="nav-link dropdown-toggle text-dark" to="#"
                                  id="cruscottoCompletoNavbarDropdown"
                                  role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
                                CRUSCOTTO COMPLETO
                            </Link>
                            <div className="dropdown-menu" aria-labelledby="cruscottoCompletoNavbarDropdown">
                                {
                                    //creazione di un link per ogni nome di proprietà disponibile
                                    this.state.nomeProprietaGrafici.map(
                                        (beName: string,
                                         feName: string) =>
                                        {
                                            return (
                                                <Fragment>
                                                    <Link key={beName}
                                                          className="dropdown-item text-center"
                                                          to={"/entsorgafin/performanceImpianto"}>${feName}</Link>
                                                    <div className="dropdown-divider" />
                                                </Fragment>
                                            );
                                        }
                                    )
                                }
                            </div>
                        </li>
                    </ul>
                    {/*<ul className="nav navbar-nav float-right-element">*/}
                    {/*<li>*/}
                    {/*    /!*<h6 className="no-margin vertical-align-text mr-1">Utente attivo:*!/*/}
                    {/*    /!*                                                   ${authenticationService.getCurrentUserName()} </h6>*!/*/}
                    {/*</li>*/}
                    {/*<li>*/}
                    {/*<button className="btn btn-sm btn-dark" onChange={this.logout}>Logout</button>*/}
                    {/*</li>*/}
                    {/*</ul>*/}
                </div>
            </nav>
        );
    }
}

export default Header;

Login.tsx component:

class Login
    extends Component<any, any>
{
    //costruttore
    constructor(props: any)
    {
        //props management
        super(props);
        
        //state management
        this.state = {
            username: '',
            password: '',
            loggedIn: authenticationService.isUserAuthenticated()
        }
        
        //methods binding
        this.handleLogin = this.handleLogin.bind(this);
        this.onChangeUserName = this.onChangeUserName.bind(this);
        this.onChangePassword = this.onChangePassword.bind(this);
    }
    
    onChangeUserName(e: any)
    {
        this.setState({
            username: e.target.value
        });
    }
    
    onChangePassword(e: any)
    {
        this.setState({
            password: e.target.value
        });
    }
    
    handleLogin(e: any)
    {
        e.preventDefault();
        
        authenticationService.authenticate(this.state.username, this.state.password).then(
            () =>
            {
                this.setState({loggedIn: true});
            }
        );
    }
    
    render()
    {
        if (this.state.loggedIn)
        {
            return (
                <Redirect push to='/entsorga/home' />
            );
        }
        
        return (
            <div className="col-md-12">
                <div className="card">
                    Carta
                </div>
                <form onSubmit={this.handleLogin}>
                    <div className="form-group">
                        <label htmlFor="username">Nome Utente</label>
                        <input type="text" className="form-control" name="username" value={this.state.username}
                               onChange={this.onChangeUserName} required={true} />
                    </div>
                    <div className="form-group">
                        <label htmlFor="password">Password</label>
                        <input type="password" className="form-control" name="password" value={this.state.password}
                               onChange={this.onChangePassword} required={true} />
                    </div>
                    <div className="form-group">
                        <button className="btn btn-primary btn-block" type="submit" value="submit">
                            Login
                        </button>
                    </div>
                </form>
            </div>
        );
    }
}

export default Login;

The homepage simply prints "Homepage" with an H1 title.

EDIT

I edited the index.tsx class this way:

    <Switch>
        <Route exact path='/'>
            {authenticationService.isUserAuthenticated() ? <Redirect to='/entsorga/home' /> : <Login />}
        </Route>
        <Route path='/entsorga' component={Header} />
        <Route path='/entsorga/home' component={Homepage} />
        <Route path='/entsorga/tracciabilitaLotti' component={tracciabilitaLotti} />
        <Route path='/entsorga/performanceImpianto' component={performanceImpianto} />
        <Route path='/entsorga/graficiCruscotto' component={graficiCruscotto} />
    </Switch>

but the problem is not solved, the places in which the redirects are:

  • in index.tsx file which redirect the user to if not logged in or to homepage otherwise

         <Route exact path='/'>
             {authenticationService.isUserAuthenticated() ? <Redirect to='/entsorga/home' /> : <Login />}
         </Route>
    
  • in login.tsx file which redirect to homepage in case of successful login:

    if (this.state.loggedIn) { return ( Redirect push to='/entsorga/home' ); }

  • header.tsx file which redirect to / in case the user is not logged in (to prevent to see the page in case a user is not logged in)

    if (this.state.loggedIn) { return ( Redirect to='/' ); }

The loggedIn state is given in the constructor of the component by a helper function calling a service, I am sure the authentication is successful as I can print the authentication token in logs.

The Header.tsx component should be dispayed along all pages, that's why i placed it in the path /entsorga and all the other components are at /entsorga/**.

EDIT 2

In the server logs, I can see that there are multiple requests from React (a lot more than the one needed) as if the Header.tsx component is continously re-rendering: I read that a component re-renders automatically when its state is modified, in Header component the state is modified in the constructor and when performing logout (not yet used), setProprietaCruscotto (used only once) and getAnniAttivita, which may be the reason of the continous reloading.

The getAnniAttivita function calls an API trhough Axios to get some data from a server which responds with a 401 (as it should for now), could Axios be the reason of the continous reloading? maybe after an error Axios retries until the call is successful making the state change multiple times?

1 Answer 1

1
<Route exact path='/'>
     {authenticationService.isUserAuthenticated() ? <Redirect to='/entsorgafin' /> : <Login />}
 </Route>
<Route path='/entsorgafin'>
    {authenticationService.isUserAuthenticated() ? <Redirect to='/entsorga/home' /> : <Redirect to='/' />}
</Route>

These two routes are always rendered since they are not wrapped in a switch.

Now You have a redirect in both routes and on opposite side of the result of isUserAuthenticated.

What happens now is that you app is getting redirected in loops since if the user is logged in, it a redirect from entsorgafin will be called, or else from the / path.

You need to add those to the switch and now the first page will only be rendered.

Sign up to request clarification or add additional context in comments.

2 Comments

I am sorry, I tried but it is not working as I am obiously missing something, I edited the question to show the changings and to give some other explanations
reading again the code led me to find a logic error which solves the automatic redirection after login, so this is actually the correct answer to the initial question

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.