【问题标题】:Is it possible to use a custom hook inside useEffect in React?是否可以在 React 的 useEffect 中使用自定义钩子?
【发布时间】:2020-03-23 01:15:51
【问题描述】:

我有一个非常基本的自定义钩子,它接受一个路径并从 firebase 返回一个文档

import React, { useState, useEffect, useContext } from 'react';
import { FirebaseContext } from '../sharedComponents/Firebase';

function useGetDocument(path) {
    const firebase = useContext(FirebaseContext)
    const [document, setDocument] = useState(null)

    useEffect(() => {
        const getDocument = async () => {
            let snapshot = await firebase.db.doc(path).get()
            let document = snapshot.data()
            document.id = snapshot.id
            setDocument(document)
        }
        getDocument()
    }, []);

    return document
}

export default useGetDocument

然后我使用useEffect作为componentDidMount/constructor来更新状态

useEffect(() => {
    const init = async () => {

      let docSnapshot = await useGetDocument("products/" + products[selectedProduct].id + "labels/list")
      if(docSnapshot) {
        let tempArray = []
        for (const [key, value] of Object.entries(docSnapshot.list)) {
          tempArray.push({id: key, color: value.color, description: value.description})
        }
        setLabels(tempArray)
      } else {
        setLabels([])
      }

      await props.finishLoading()
      await setLoading(false)
    }
    init()
  }, [])

但是,我从“throwInvalidHookError”中得到了一个 Invariant Violation,这意味着我违反了钩子的规则,所以我的问题是你是否不能在 useEffect 中使用自定义钩子,或者我做错了什么。

【问题讨论】:

    标签: reactjs react-hooks


    【解决方案1】:

    据我所知,组件中的钩子应该始终保持相同的顺序。而且由于useEffect 有时会发生,而且并非所有渲染都会破坏rules of hooks。在我看来,您的 useGetDocument 没有真正的需要。

    我提出以下解决方案:

    1. 保持useGetDocument 不变。
    2. 将您的组件更改为具有 useEffect,该组件将 document 作为依赖项。

    您的组件可能如下所示:

    const Component = (props) => {
        // Your document will either be null (according to your custom hook) or the document once it has fetched the data.
        const document = useGetDocument("products/" + products[selectedProduct].id + "labels/list");
    
        useEffect(() => {
            if (document && document !== null) {
                // Do your initialization things now that you have the document.
            }
        }, [ document ]);
    
       return (...)
    }
    

    【讨论】:

      【解决方案2】:

      当然你可以在其他钩子中调用钩子。

      不要从常规 JavaScript 函数中调用 Hooks。相反,您可以:

      ✅ 从 React 函数组件调用 Hooks。

      ✅ 从自定义 Hooks 调用 Hooks(我们将在下一页了解它们)。

      但是……

      你没有在另一个钩子中使用一个钩子

      您意识到您传递给 useEffect 的是一个回调,因此您在回调主体中使用自定义挂钩而不是挂钩 (useEffect)。

      如果你碰巧使用 ESLint 和 react-hooks 插件,它会警告你:

      ESLint: React Hook "useAttachDocumentToProspectMutation" cannot be called inside a callback. React Hooks must be called in a React function component or a custom React Hook function.(react-hooks/rules-of-hooks)

      话虽如此,您根本不需要 useEffect。并且 useGetDocument 不会返回一个承诺,而是一个文档。

      调用你的钩子时

      const document = useGetDocument("products/" + products[selectedProduct].id + "labels/list");
      

      它将在第一次返回未定义,然后根据@ApplePearPerson 的回答为后续呈现的文档。

      【讨论】:

        【解决方案3】:

        你不能在另一个钩子中使用一个钩子,因为它违反了规则Call Hooks from React function components,而你传递给useEffect的函数是一个普通的javascript函数。

        你可以做的是在另一个自定义钩子中调用一个钩子。

        你需要做的是在组件内部调用useGetDocument并将结果传递到useEffect依赖数组中。

        let docSnapshot = await useGetDocument("products/" + products[selectedProduct].id + "labels/list")
        
        useEffect(() => { ... }, [docSnapshot])
        

        这样,当docSnapshot 改变时,你的useEffect 会被调用。

        【讨论】:

        • 我明白了,但是假设我想通过再次调用 useGetDocument 来获取文档的更新版本。这用钩子是不可能的吗?
        • @Andreas 如果你想get an updated version of the document, by calling,你不应该创建一个自定义钩子,而只是一个获取文档的普通函数。如果您想使用钩子,您应该在您的useGetDocument 中的文档更改时实现一个事件监听器,它会更新状态,因此当文档更改时,它会更新docSnapshot 并再次调用您的useEffect
        • 很抱歉,因为我对firebase不够了解,所以不能给出完整的代码,但我知道你需要什么逻辑,可以引导你找到答案。
        猜你喜欢
        • 2021-09-10
        • 2020-02-19
        • 1970-01-01
        • 2020-08-27
        • 2021-10-22
        • 2020-12-05
        • 1970-01-01
        • 2021-05-31
        • 2020-01-26
        相关资源
        最近更新 更多