【发布时间】: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Á 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