尽管状态发生了变化,但自定义钩子不会触发组件重新渲染

Posted

技术标签:

【中文标题】尽管状态发生了变化,但自定义钩子不会触发组件重新渲染【英文标题】:Custom hook not triggering component rerender despite changes in state 【发布时间】:2020-12-02 06:48:38 【问题描述】:

上下文:我有一个自定义挂钩来获取 Firestore 文档。它监视文档更改,然后返回它和加载状态。我在另一个组件中使用了这个钩子。

问题:更改挂钩返回的状态值(loadingdocument)不会触发组件使用挂钩重新呈现并查看更新的值。

我的发现:其他自定义钩子可以工作并触发渲染,所以useDocument 钩子肯定有问题。如果另一个钩子触发了重新渲染,那么loading 才会最终更新。 loadingdocument 的值确实在钩子中按预期更新,但组件的重新渲染仍然不会触发。

重要说明: useFirebase() 是在 useDocument 内调用的钩子,其状态使用 constate 库提升到上下文中。所以从技术上讲,这个钩子是一些提供者的消费者,它位于跟踪 firebase 库的树的上层。这是为了避免不必要的网络调用。


export function useDocument() 
  const  auth, firestore  = useFirebase() // asynchronously fetches firebase modules
  const [loading, setLoading] = useState(true)
  const [document, setDocument] = useState()

  let unsubscribe = null

  

  useEffect(() => 
    let unsubscribe = null
    const watchDocument = () =>
      params?.query.onSnapshot(
        (doc) => 
          setDocument(doc.data() as T | undefined)
          setLoading(false)
        ,
        (err) => 
          console.log(err)

          setError(err.message)
        
      )
    if (firestore && auth?.currentUser) 
      unsubscribe = watchDocument()
    
    return () => 
      unsubscribe && unsubscribe()
    
  , [firestore, auth?.currentUser])

  return 
    document,
    loading
  


const MyComponent = () => 
  const document, loading = useDocument()
  console.log(loading) // changes to "loading" doesn't trigger rerender of this component

【问题讨论】:

【参考方案1】:

这个sn-p的错误很少,主要是变量unsubscribe总是undefined,因为它甚至不在useEffect的范围内。

这可能是一个有效的修复(从未使用过 firebase,仅通过查看代码):

export function useDocument() 
  const  auth, firestore  = useFirebase();
  const [loading, setLoading] = useState(true);
  const [document, setDocument] = useState();

  useEffect(() => 
    const watchDocument = async () => 
      const unsubscribe = await params?.query.onSnapshot(
        (doc) => 
          setDocument(doc.data());
          setLoading(false);
        ,
        (err) => 
          console.log(err);
          setError(err.message);
        
      );
    ;

    let unsubscribe = null;
    if (firestore && auth) 
      unsubsribe = watchDocument();
    
    return () => 
      unsubscribe?.();
    ;
  , [firestore, auth]);

  return 
    document,
    loading,
  ;


const MyComponent = () => 
  const  document, loading  = useDocument();
  console.log(loading);
;

【讨论】:

在深入挖掘并看到我确实在错误的地方定义了取消订阅后,我删除了我的原始评论。我用正确的代码更新了我的问题。谢谢!重新渲染行为最终成为一个参考问题。我还意识到我在问题中删除了太多代码,因此我对其进行了更新以反映问题所在。【参考方案2】:

所以问题是依赖auth?.currentUserauth 本身在状态更改时会正确触发重新渲染。问题似乎是无法检测到auth?.currentUser 中的更改。

我的假设是,这是因为 auth 是被跟踪的内容,即使 currentUser 由 firebase 库更新,对 auth 的引用也不会改变。我最终将currentUser 移动到了它自己的useState 中,现在组件正确地重新渲染了。

【讨论】:

以上是关于尽管状态发生了变化,但自定义钩子不会触发组件重新渲染的主要内容,如果未能解决你的问题,请参考以下文章

尽管依赖项没有变化,useEffect 运行无限循环

我的 Redux 状态发生了变化,为啥 React 没有触发重新渲染?

React组件生命周期

React生命周期, setState、props改变触发的钩子函数

反应钩子如何确定它们的组件?

Redux 状态发生了变化,为啥不触发重新渲染? (Redux 传奇)