React JS:渲染的钩子比预期的要少。这可能是由于意外提前退货声明造成的
Posted
技术标签:
【中文标题】React JS:渲染的钩子比预期的要少。这可能是由于意外提前退货声明造成的【英文标题】:React JS: Rendered fewer hooks than expected. This may be caused by an accidental early return statement 【发布时间】:2020-05-10 22:57:25 【问题描述】:我正在尝试为 React JS 项目进行身份验证,但出现以下错误:
渲染的钩子比预期的要少。这可能是由于意外的提前退货声明造成的。
我正在阅读其他问题(例如 this 和 this),但我想我的情况有所不同,因为我在创建组件后设置了所有 useState
。
我使用了不同的方法来完成这项工作,但它们都不适合我。
我在ContextWrapper
组件中添加了这样的方法:
import React, useState, useEffect from "react";
import useCookies from "react-cookie";
import jwt from "jsonwebtoken";
import fetchLogin, fetchVerify from "../fake-server";
import Context from "./context";
export default ( children ) =>
const [token, setToken] = useState(null);
const [message, setMessage] = useState(null);
const [loading, setLoading] = useState(false);
const [cookies, setCookie, removeCookie] = useCookies(["token"]);
useEffect(() =>
console.log(cookies);
if (cookies.token)
setToken(cookies.token);
, []);
useEffect(() =>
if (token)
setCookie("token", JSON.stringify(token), path: "/" );
else
removeCookie("token");
console.log(token);
, [token]);
function login(email, password)
fetchLogin(email, password)
/*.then(response => response.json())*/
.then(data =>
const decoded = jwt.decode(data.token);
const token =
token: data.token,
...decoded
;
setToken(token);
)
.catch(error =>
console.log("error", error);
setMessage(
status: 500,
text: error
);
);
function verify()
fetchVerify(cookies.token)
/*.then(response => response.json())*/
.then(data =>
setToken(data);
)
.catch(error =>
setToken(null);
setMessage(
status: 500,
text: error
);
);
function logout()
setToken(false);
const value =
login,
verify,
logout,
token,
message,
setMessage,
loading,
setLoading
;
return <Context.Provider value=value>children</Context.Provider>;
;
然后这是我的Login
组件:
import React, useState, useContext, useEffect from "react";
import
Grid,
Card,
Typography,
CardActions,
CardContent,
FormControl,
TextField,
Button
from "@material-ui/core";
import Redirect from "react-router-dom";
import makeStyles from "@material-ui/core/styles";
import Context from "../context/context";
const useStyles = makeStyles(theme => (
root:
height: "100vh",
display: "flex",
justifyContent: "center",
alignContent: "center"
,
title:
marginBottom: theme.spacing(2)
,
card: ,
formControl:
marginBottom: theme.spacing(1)
,
actions:
display: "flex",
justifyContent: "flex-end"
));
export default ( history, location ) =>
const context = useContext(Context);
const classes = useStyles();
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
if (context.token)
return <Redirect to="/" />;
useEffect(() =>
context.setLoading(false);
, []);
function onSubmit(e)
e.preventDefault();
context.login(email, password);
return (
<Grid container className=classes.root>
<Card className=classes.card>
<form onSubmit=onSubmit>
<CardContent>
<Typography variant="h4" className=classes.title>
Login
</Typography>
<FormControl className=classes.formControl fullWidth>
<TextField
type="text"
variant="outlined"
label="Email"
value=email
onChange=e => setEmail(e.target.value)
/>
</FormControl>
<FormControl className=classes.formControl fullWidth>
<TextField
type="password"
variant="outlined"
label="Password"
value=password
onChange=e => setPassword(e.target.value)
/>
</FormControl>
</CardContent>
<CardActions className=classes.actions>
<Button type="submit" variant="contained" color="secondary">
Login
</Button>
</CardActions>
</form>
</Card>
</Grid>
);
;
这里我创建了this sandbox 来重现触发login()
方法时发生的错误。那么,我做错了什么?
【问题讨论】:
登录中useEffect
之前的return <Redirect to="/" />;
是问题所在。
【参考方案1】:
将你的条件移到useEffect
下。
useEffect(() => )
if (context.token)
return <Redirect to="/" />;
因为您的if
条件影响了它下面的后续挂钩,因为如果context.token
为真,它将不会运行。
这违反了Rules of Hooks。
专业提示:按照 React-Docs 中的建议添加 ESlint 插件,以便在您编码时获取这些警告/错误。
【讨论】:
好吧......也许这行得通,但现在我得到Maximum update depth exceeded.
由于无限重新加载......
@Maramal 好吧,那是另一个问题,不是吗?您的 Home
组件在组件主体内有副作用(context.verify()
),将其移至 useEffect
并重新考虑您的架构
我猜这是另一个问题......所以感谢您解决我的第一个问题。
@Maramal 没问题,答案就在这里,你不能在组件体内有任何副作用,所以没有网络调用,没有状态更新,将它们移动到 useEffect
。如果用户未登录,更好的方法是不渲染路由【参考方案2】:
在我的例子中,我使用useWindowDimension
来获得View
风格内的高度。这个错误花费了我超过 3 天的无休止搜索,直到我意识到 useWindowDimension 也是一个钩子。所以正确的使用方法是这样的:
const window = useWindowDimension();
return (
<View style= height: window.height, />
);
【讨论】:
【参考方案3】:将重定向逻辑移到钩子之后。
useEffect(() =>
context.setLoading(false);
, []);
if (context.token)
return <Redirect to="/" />;
如果userIsValid
id 不是true
,则在您的主组件中重定向用户。并且此文件将始终未定义,因为它未在context
中定义。在上下文中添加此字段并尝试。
希望这能解决问题。
【讨论】:
【参考方案4】:来自控制台的错误会告诉您问题出在哪里。您不能有条件地创建挂钩。这个钩子是个问题,需要出现在return语句之前。这可能意味着您应该更改加载条件以按预期工作。
useEffect(() =>
context.setLoading(false);
, []);
【讨论】:
我在哪里有条件地创建一个钩子? 我的评论中你有 if 语句在钩子之前返回。这意味着只有在您的 if 条件(context.token)为真之后才会创建钩子。以上是关于React JS:渲染的钩子比预期的要少。这可能是由于意外提前退货声明造成的的主要内容,如果未能解决你的问题,请参考以下文章
渲染的钩子比预期的要少。这可能是由于意外提前退货声明造成的。基于状态的条件渲染组件
React/NextJS 使用 UseEffect 错误:渲染的钩子比上一次渲染期间更多
未处理的拒绝(错误):渲染的钩子比上一次渲染时更多(React Interface GrandStack with ApolloClient)