【问题标题】:React JS: Rendered fewer hooks than expected. This may be caused by an accidental early return statementReact JS:渲染的钩子比预期的要少。这可能是由于意外提前退货声明造成的
【发布时间】:2020-05-10 22:57:25
【问题描述】:

我正在尝试为 React JS 项目进行身份验证,但出现以下错误:

渲染的钩子比预期的要少。这可能是由于意外的提前退货声明造成的。

我正在阅读其他问题(例如 thisthis),但我想我的情况有所不同,因为我在创建组件后设置了所有 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 &lt;Redirect to="/" /&gt;; 是问题所在。

标签: javascript reactjs state react-context


【解决方案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)为真之后才会创建钩子。
      猜你喜欢
      • 2021-11-15
      • 2021-12-14
      • 2022-11-20
      • 2021-10-03
      • 2022-06-13
      • 2019-04-27
      • 1970-01-01
      • 2021-07-17
      • 2021-07-19
      相关资源
      最近更新 更多