如何在 Typescript 中正确使用 React Context Provider?

How to properly use React Context Provider with Typescript?

我已经持续数小时尝试寻找我认为可行但无法正确完成的东西。你能帮我解决这个问题吗?

我这里有我的文件浏览器上下文,我想在其中传递 folderData 并将其调用到另一个函数。

import { createContext, useContext } from 'react';

export interface fileBrowserContext {
  folderData: string;
}

export const FileBrowserContext = createContext<fileBrowserContext>({
  folderData: '',
});

export const useFileBrowserContext = () => useContext(FileBrowserContext);

这是我正在使用的组件。我也在使用 react-hook-form,当我点击提交按钮时它正在工作。

    const FileBrowser = () => {

      const { control, watch, handleSubmit, register, reset, errors } = useForm({
        reValidateMode: 'onChange',
        defaultValues: {
          createFolder: '',
        },
      });

      const [folderData, setFolderData] = useState<string>('');

      async function onSubmit(formData: any) {
          if (Object.keys(errors).length === 0) {
            /* Create folder */
            setFolderData(formData.createFolder);

            reset({ createFolder: '' });
          }

          handleToastSuccess({
            message: 'Folder Created',
          });
      }


      function renderForm() {
         return (
          <Form onSubmit={handleSubmit(onSubmit)} className="CreateFolderForm">
            <Form.Group>
              <Form.Label>Create Folder</Form.Label>
              <Form.Control
                id="createFolder"
                name="createFolder"
                ref={register({
                  required: {
                    value: true,
                    message: 'This field is required.',
                  },
                })}
              />
            </Form.Group>

              <Button
                variant="contained"
                color="primary"
                type="submit"
              >
                Create
              </Button>
          </Form>
        );
      }

      const {
        isCreateFolderOpen,
        setIsCreateFolderOpen,
        handleFileCallback,
      } = useFileActionHandler(
        createFolder,
      );

      return (
        <FileBrowserContext.Provider value={{ folderData: folderData }}>
         <>
         <h1> File Browser </h1>
          {folderData + ','}
         </>
           <CustomModal
            showModal={isCreateFolderOpen}
            setShowModal={setIsCreateFolderOpen}
            modalHeading="Create Folder"
            modalContent={renderForm()}
          />
        </FileBrowserContext.Provider>
       )
    }

我正在 utils.tsx 上调用 useFileBrowserContext()。我需要来自上面组件的 folderData,我计划在其中调用使用上下文,以便能够将文件夹保存在我正在使用的 Chonky 库中。

export const useFileActionHandler = (
  setCurrentFolderId: (folderId: string) => void,
  createFolder: (folderName: string) => void,
) => {

  const [isCreateFolderOpen, setIsCreateFolderOpen] = useState<boolean>(false);
  const { folderData } = useFileBrowserContext();


  const handleFileCallback = useCallback(
    (data: ChonkyFileActionData) => {
      if (data.id === ChonkyActions.CreateFolder.id) {
        // CONTEXT BEING CALLED HERE
        setIsCreateFolderOpen(true);
        if (folderData) createFolder(folderData);

      } 

      console.log('file browser', folderData);
    },
    [
      createFolder,
    ],
  );

  return {
    handleFileCallback,
    isCreateFolderOpen,
    setIsCreateFolderOpen,
  };
};

我的控制台没有错误,但行为是当我在 utils.tsx 中记录 folderData 时,它 returns 一个空字符串,我认为这意味着 folderData 从该组件不工作。在此先感谢您的帮助。

为了让组件使用 useContext,它们必须是 Context.Providerdescendent。在您的情况下,您尝试在 FileBrowser 组件中使用 FileBrowserContext,但它不是 Context.Provider 的后代,因此它接收 folderData 的默认值(空字符串)。

所以,目前你有:

<App>
  <FileBrowser>
    <FileBrowserContext.Provider>
      {/* ... */}
    </FileBrowserContext.Provider>
  </FileBrowser>
</App>

但你需要

<App>
  <FileBrowserContext.Provider>
    <FileBrowser>
      {/* ... */}
    </FileBrowser>
  </FileBrowserContext.Provider>
</App>

解决方法是将您的 Context.Provider 组件向上移动一个级别并将其呈现为 FileBrowser 的父级,而不是让 FileBrowser 本身呈现它。您可以将其包装到一个自定义组件中,该组件处理设置正确的 value,如下所示:

const FileBrowserContextProvider = () => {
  const [folderData, setFolderData] = useState('');

  return (
    <FileBrowserContext.Provider value={{ folderData, setFolderData }}>
      {children}
    </FileBrowserContext.Provider>
  );
};

const App = () => {
  return (
    <FileBrowserContextProvider>
      <FileBrowser /> {/* <-- now FileBrowser can safely useContext
                              since it's a descendent of the Provider */}
    </FileBrowserContextProvider>
  );
};