【问题标题】:React Hook "useEffect" is called conditionally有条件地调用 React Hook “useEffect”
【发布时间】:2019-12-28 10:54:43
【问题描述】:

React 抱怨下面的代码,说它 useEffect 被有条件地调用:

import React, { useEffect, useState } from 'react'
import VerifiedUserOutlined from '@material-ui/icons/VerifiedUserOutlined'
import withStyles from '@material-ui/core/styles/withStyles'
import firebase from '../firebase'
import { withRouter } from 'react-router-dom'

function Dashboard(props) {
  const { classes } = props
  
  const [quote, setQuote] = useState('')

    if(!firebase.getCurrentUsername()) {
        // not logged in
        alert('Please login first')
        props.history.replace('/login')
        return null
    }

    useEffect(() => {
        firebase.getCurrentUserQuote().then(setQuote)
    })

    return (
        <main>
            // some code here
        </main>
    )

    async function logout() {
        await firebase.logout()
        props.history.push('/')
    }
}

export default withRouter(withStyles(styles)(Dashboard))

这会返回错误:

有条件地调用 React Hook "useEffect"。反应钩子 必须在每个组件渲染中以完全相同的顺序调用。

有谁知道这里的问题是什么?

【问题讨论】:

  • 返回空值?从if条件?一个组件只能返回有效的 JSX
  • @NatGeo null 是一个有效的 JSX 表达式 stackoverflow.com/q/42083181/1176601 ... 但是返回后的代码只有在 if 语句为 false 时才会执行,类似于 else { ... } - aka "conditionally " 这是被钩子规则所禁止的

标签: javascript reactjs react-hooks


【解决方案1】:

不要在循环、条件或嵌套函数中调用 Hooks。相反,请始终在 React 函数的顶层使用 Hooks。 您可以关注文档here

我在上面的代码中找不到用例。如果您需要在firebase.getCurrentUsername() 的返回值更改时运行效果,您可能希望在if 条件之外使用它,例如:

useEffect(() => {
    firebase.getCurrentUserQuote().then(setQuote)
}, [firebase.getCurrentUsername()]);

【讨论】:

  • 虽然属实,但这并不能解决问题。
【解决方案2】:

这里的问题是,当我们从 if block 返回 null 时,useEffect 钩子代码将无法访问,因为我们在它之前返回,因此有条件调用它的错误。

您可能希望先定义所有挂钩,然后开始编写渲染逻辑,无论是 null 或空字符串,还是有效的 JSX。

【讨论】:

    【解决方案3】:

    在包含returnif 语句之后,您的代码等效于else 分支:

    if(!firebase.getCurrentUsername()) {
        ...
        return null
    } else {
        useEffect(...)
        ...
    }
    

    这意味着它是有条件地执行的(仅当return 未被执行时)。

    修复:

    useEffect(() => {
      if(firebase.getCurrentUsername()) {
        firebase.getCurrentUserQuote().then(setQuote)
      }
    }, [firebase.getCurrentUsername(), firebase.getCurrentUserQuote()])
    
    if(!firebase.getCurrentUsername()) {
      ...
      return null
    }
    

    【讨论】:

      【解决方案4】:

      我认为有一种方法可以有条件地调用钩子。你只需要从那个钩子中导出一些成员。将此sn-p复制粘贴到codesandbox中:

      import React from "react";
      import ReactDOM from "react-dom";
      
      function useFetch() {
        return {
          todos: () =>
            fetch("https://jsonplaceholder.typicode.com/todos/1").then(response =>
              response.json()
            )
        };
      }
      
      const App = () => {
        const fetch = useFetch(); // get a reference to the hook
      
        if ("called conditionally") {
          fetch.todos().then(({title}) => 
            console.log("it works: ", title)); // it works:  delectus aut autem  
        }
      
        return null;
      };
      
      const rootElement = document.getElementById("root");
      ReactDOM.render(<App />, rootElement);
      

      这是一个带有包装 useEffect 的示例:

      import React, { useEffect } from "react";
      import ReactDOM from "react-dom";
      
      function useWrappedEffect() {
        const [runEffect, setRunEffect] = React.useState(false);
      
        useEffect(() => {
          if (runEffect) {
            console.log("running");
            setRunEffect(false);
          }
        }, [runEffect]);
      
        return {
          run: () => {
            setRunEffect(true);
          }
        };
      }
      
      const App = () => {
        const myEffect = useWrappedEffect(); // get a reference to the hook
        const [run, setRun] = React.useState(false);
      
        if (run) {
          myEffect.run();
          setRun(false);
        }
      
        return (
          <button
            onClick={() => {
              setRun(true);
            }}
          >
            Run
          </button>
        );
      };
      
      const rootElement = document.getElementById("root");
      ReactDOM.render(<App />, rootElement);
      

      【讨论】:

      • 您的useFetch 不是钩子。它不使用 useStateuseEffect(或任何其他 React 核心挂钩)
      • 我将示例保持在最低限度。如果你喜欢,请随意在里面使用 useState ?
      • 问题是关于useEffect,你能更新你的例子来使用它吗?因为我不知道它会如何工作。
      • 谢谢!但它具有误导性,您不必“必须从该钩子中导出一些成员”来有条件地触发 useEffect。据我了解,关键是将if 放在useEffect 内。 (我也看到你使用了两种状态,我们不需要两者)
      • 是的,关键是在useEffect 中添加if 语句。我知道我的答案并不完全是问题的解决方案,但总的来说,这是一个强大的概念,因为您可以在单独的自定义钩子中提取逻辑(实际上不是原生的 useEffect)并在任何地方使用它
      【解决方案5】:

      我遇到了同样的错误消息,其中变量声明的顺序是错误的来源:

      不好的例子

      if (loading) return <>loading...</>;
      if (error) return <>Error! {error.message}</>;
      
      const [reload, setReload] = useState(false);
      

      好例子

      const [reload, setReload] = useState(false);
      
      if (loading) return <>loading...</>;
      if (error) return <>Error! {error.message}</>;
      

      钩子需要在潜在的条件返回块之前创建

      【讨论】:

        猜你喜欢
        • 2020-01-06
        • 2021-03-17
        • 2022-12-21
        • 1970-01-01
        • 2021-12-03
        • 2019-10-08
        • 2020-11-27
        • 1970-01-01
        • 2021-02-23
        相关资源
        最近更新 更多