DiegLuthor
DiegLuthor

Reputation: 325

React error --> Error: Maximum update depth exceeded

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:

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:

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?

Upvotes: 0

Views: 198

Answers (1)

Domino987
Domino987

Reputation: 8774

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

Upvotes: 1

Related Questions