【问题标题】:Using React Context in a custom hook always returns undefined在自定义钩子中使用 React Context 总是返回 undefined
【发布时间】:2022-01-26 18:15:31
【问题描述】:

我正在尝试创建一个自定义挂钩,最终将在 NPM 上打包并在我工作的公司的项目内部使用。基本思想是我们希望包暴露一个提供者,它在挂载时将向服务器发出请求,该请求返回一个权限字符串数组,然后通过上下文将这些权限字符串提供给子组件。我们还需要一个函数can,它可以在提供程序中调用,它将接受一个字符串参数并根据上下文提供的权限数组中是否存在该字符串返回一个布尔值。

我一直在关注this article,但任何时候我从提供程序内部调用can,上下文总是以未定义的形式返回。下面是一个非常简化的版本,没有功能,我一直在尝试弄清楚发生了什么:

useCan/src/index.js:

import React, { createContext, useContext, useEffect } from 'react';

type CanProviderProps = {children: React.ReactNode}

type Permissions = string[]

// Dummy data for fake API call
const mockPermissions: string[] = ["create", "click", "delete"]
  
const CanContext = createContext<Permissions | undefined>(undefined)

export const CanProvider = ({children}: CanProviderProps) => {
  let permissions: Permissions | undefined 

  useEffect(() => {
    permissions = mockPermissions
    // This log displays the expected values
    console.log("Mounted. Permissions: ", permissions)
  }, [])

  return <CanContext.Provider value={permissions}>{children}</CanContext.Provider>
}

export const can = (slug: string): boolean => {
  const context = useContext(CanContext)
  
  // This log always shows context as undefined
  console.log(context)

  // No functionality built to this yet. Just logging to see what's going on.
  return true
}

然后是我正在测试的简单 React 应用程序:

useCan/example/src/App.tsx:

import React from 'react'

import { CanProvider, can } from 'use-can'

const App = () => {
  return (
    <CanProvider>
      <div>
        <h1>useCan Test</h1>
        {/* Again, this log always shows undefined */}
        {can("post")}
      </div>
    </CanProvider>
  )
}

export default App

我哪里错了?这是我第一次真正使用 React 上下文,所以我不确定在哪里查明问题出在哪里。任何帮助,将不胜感激。谢谢。

【问题讨论】:

  • let permissions: Permissions | undefined; 你把这个变量初始化为未定义,当 CanProvider 返回上下文提供者时它仍然未定义。 useEffect 只发生在渲染之后useEffect 的目标是什么?
  • @NicholasTower 最终,当提供程序首次挂载时,对 API 的调用将在 useEffect 中进行,所以我想在那里分配虚拟数据来模拟它。
  • 听起来像permissions 应该是一个状态,而不是一个局部变量。一旦数据可用,您将需要设置状态才能重新渲染。
  • @NicholasTower 是的,现在意识到这可能是问题所在。试图让一个精简的版本开始运行,但我认为在这种情况下,试图过于简单地保持它是破坏它的原因。

标签: reactjs typescript npm react-hooks react-context


【解决方案1】:

您的实现存在两个问题:

  1. 在您的CanProvider 中,您正在使用= 重新分配permissions 中的值。这不会触发 Provider 组件中的更新。我建议使用useState 而不是let=
const [permissions, setPermissions] = React.useState<Permissions | undefined>();

useEffect(() => {
  setPermissions(mockPermissions)
}, []);

这将使 Provider 在permissions 更改时正确更新。

  1. 您正在从常规函数调用挂钩(can 函数调用 useContext)。这违反了 Hooks 的主要规则之一。你可以在这里了解更多信息:https://reactjs.org/docs/hooks-rules.html#only-call-hooks-from-react-functions

我建议创建一个自定义挂钩函数,为您提供所需的 can 函数。

例如这样的东西

const useCan = () => {
  const context = useContext(CanContext)
  return () => {
    console.log(context)

    return true
  }
}

然后,您应该在提供程序内部的某些组件的根级别(根据钩子规则)使用全新的钩子。例如,为内容提取一个组件,如下所示:

const Content = (): React.ReactElement => {
  const can = useCan();

  if(can("post")) {
    return <>Yes, you can</>
  }

  return null;
}

export default function App() {
  return (
    <CanProvider>
      <div>
        <h1>useCan Test</h1>
        <Content />
      </div>
    </CanProvider>
  )
}

【讨论】:

    【解决方案2】:

    您应该使用state 来管理权限。 看下面的例子:

    export const Provider: FC = ({ children }) => {
      const [permissions, setPermissions] = useState<string[]>([]);
    
      useEffect(() => {
        // You can fetch remotely
        // or do your async stuff here
        retrivePermissions()
          .then(setPermissions)
          .catch(console.error);
      }, []);
    
      return (
        <CanContext.Provider value={permissions}>{children}</CanContext.Provider>
      );
    };
    
    export const useCan = () => {
      const permissions = useContext(CanContext);
    
      const can = useCallback(
        (slug: string) => {
          return permissions.some((p) => p === slug);
        },
        [permissions]
      );
    
      return { can };
    };
    

    使用useState 可以强制组件更新值。 你可能想read more here

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-03-25
      • 2020-08-27
      • 2021-02-14
      • 1970-01-01
      • 2021-03-29
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多