【问题标题】:React error --> Error: Maximum update depth exceeded反应错误->错误:超过最大更新深度
【发布时间】:2021-06-20 00:17:01
【问题描述】:

早上好, 我正在尝试开发一个 React 应用程序以通过 REST API 与后端进行通信。

应用架构还使用 React Router,并使用服务器提供的 JWT 令牌实现身份验证功能并存储在 SessionStorage 中。

基本上:

  • 用户连接到应用程序
  • 如果用户没有登录,他会被重定向到登录页面
  • 登录后会重定向到路径/entsorga/home的主页,应该会显示路径为/entsorga的组件和路径为/entsorga/home的组件(如果对React Router的工作有很好的理解的话)

通过启动应用程序,用户被正确重定向到登录页面并可以正确验证,验证后页面 url 以正确的方式更改但组件未呈现并显示后续错误。

虽然我对 React 完全陌生,但我遇到了错误:

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.

我知道错误来自一个函数不断调用setState这一事实,我已经尝试一一消除对该函数的调用,但这并不能解决问题,所以它不应该是关于我的功能。

React Router 是否有一些我可能错过的东西,它与组件生命周期钩子交互并导致此错误发生?

index.tsx 页面:

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 组件:

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 组件:

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;

主页只是打印带有 H1 标题的“主页”。

编辑

我以这种方式编辑了 index.tsx 类:

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

但是问题没有解决,重定向的地方是:

  • 在 index.tsx 文件中,如果未登录则将用户重定向到主页,否则会重定向到主页

         <Route exact path='/'>
             {authenticationService.isUserAuthenticated() ? <Redirect to='/entsorga/home' /> : <Login />}
         </Route>
    
  • 在login.tsx文件中,登录成功后重定向到主页:

    如果 (this.state.loggedIn) { 返回 ( 重定向推送到='/entsorga/home' ); }

  • header.tsx 文件,在用户未登录的情况下重定向到 /(以防止在用户未登录的情况下看到页面)

    如果 (this.state.loggedIn) { 返回 ( 重定向到='/' ); }

loggedIn 状态由调用服务的辅助函数在组件的构造函数中给出,我确信身份验证成功,因为我可以在日志中打印身份验证令牌。

Header.tsx 组件应该在所有页面上显示,这就是为什么我将它放在路径 /entsorga 中,而所有其他组件都位于 /entsorga/**。

编辑 2

在服务器日志中,我可以看到来自 React 的多个请求(比需要的请求多得多),就好像 Header.tsx 组件正在不断地重新渲染:我读到一个组件会自动重新渲染它的状态被修改了,在Header组件中,在构造函数和执行注销时(尚未使用),setProprietaCruscotto(仅使用一次)和getAnniAttivita时修改了状态,这可能是不断重新加载的原因。

getAnniAttivita 函数通过 Axios 调用 API 以从响应 401 的服务器获取一些数据(目前应该如此),Axios 可能是持续重新加载的原因吗?可能在出错之后Axios会重试直到调用成功多次改变状态?

【问题讨论】:

    标签: reactjs typescript react-router react-hooks


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

    这两个路由总是被渲染,因为它们没有被包裹在一个开关中。

    现在,您在两条路线和isUserAuthenticated 结果的另一侧都有重定向。

    现在发生的情况是,您的应用在循环中被重定向,因为如果用户登录,它将调用来自 entsorgafin 的重定向,或者来自 / 路径的重定向。

    您需要将它们添加到开关中,现在将只呈现第一页。

    【讨论】:

    • 对不起,我试过了,但它不起作用,因为我明显遗漏了一些东西,我编辑了问题以显示变化并给出其他解释
    • 再次阅读代码导致我发现一个逻辑错误解决了登录后自动重定向,所以这实际上是最初问题的正确答案
    猜你喜欢
    • 2021-05-15
    • 1970-01-01
    • 2021-10-13
    • 2021-09-06
    • 1970-01-01
    • 2020-07-16
    • 2021-10-01
    • 2019-10-09
    • 1970-01-01
    相关资源
    最近更新 更多