首页 > 解决方案 > 使用 react recoil 和 react-hook-form 执行获取请求

问题描述

我正在尝试使用 reactrecoil进行状态管理,到目前为止我很喜欢它,但是目前我正在尝试将它与之集成,但react-hook-form我找不到适合我的设置。

很简单,我想执行一个函数,onSubmit在完成一个Form. 我已经设置了一个selector来发出异步请求并更新一个atom与上传进度相对应的,但是我在执行该函数时遇到了麻烦,因为它在回调而不是功能组件中被调用,我试图将它提取到一个自定义钩子,但这也无效;我已经提供了下面的代码,如果有人有任何想法,请告诉我!

当前实现的示例错误:

React Hook "useRecoilValue" is called in function "submit" which is neither a React function component or a custom React Hook function

代码


export const videoUploadProgressState = atom({
    key: "videoProgress",
    default: {progress: 0.0}
});

export const videoUploadState = selectorFamily({
    key: 'videoUploadState',
    get: (data : {[key:string] : any}) => async ({get}) => {
        const storage = get(firestoreStorageState);
        const task = storage.ref(data.path).put(data.file);
        const [progress, setProgress] = useRecoilState(videoUploadProgressState)
        task.on("state_changed", snapshot =>{
            switch(snapshot.state){
                case 'running':
                    setProgress({progress: snapshot.bytesTransferred / snapshot.bytesTransferred});
                    break;
                }
            }
        );
        const response = await fetch(
            "http://*********:80/streaming/create-video-asset",
            {
                method:'post', 
                body:JSON.stringify({url: (await task).ref.getDownloadURL()})
            });
        console.log(response.json());
        return response.json();
        
    }
})

const UploadMovieComponent = () => {
  const [title, setTitle] = useState("Select movie");
  const {register, handleSubmit} = useForm();
  const movies = useRecoilValue(movieState);
  const progress = useRecoilValue(videoUploadProgressState)
  const submit = (data) => {
    setTitle(
      useRecoilValue(
        videoUploadState(
          {
            path: `movies/${data.id}/${data.movieFile[0].name}`, 
            file: data.movieFile[0]
          }
        )
      )
    );
  }
  return(
    <div>
      <Form onSubmit={handleSubmit(submit)}>
  <Form.Group controlId="exampleForm.SelectCustom">
    <Form.Label>Select movie</Form.Label>
    <Form.Control as="select" ref={register} name="id" custom>
    {movies.map(movie=> <option value={movie.id}>{movie.title}</option>)}
    </Form.Control>
  </Form.Group>
  <Form.Group>
    <Form.File name="movieFile" ref={register}/>
  </Form.Group>
  <Button type="submit" variant="outline-warning">Upload</Button>
</Form>
<ProgressBar className="mt-2" now={progress.progress} />
    </div>
  );
}

标签: reactjstypescriptreact-hook-formrecoiljs

解决方案


解决方案涉及使用set选择器上的属性来传递data包含我要发送到url.

这样你就可以使用useSetRecoilState钩子来获取一个可以在组件树范围之外使用的回调:

/**
* The components
*/

const CineburProgressBar = () => {
  const [progress, setProgress] = useState(0.0);
  const task = useRecoilValue(videoUploadProgressState);
  task?.on("state_changed", (snapshot) => {
    setProgress((snapshot.bytesTransferred / snapshot.totalBytes) * 100);
  });
  return (
    <React.Suspense fallback={<div>Waiting...</div>}>
      <ProgressBar className="mt-2" now={progress} />
    </React.Suspense>
  );
};

const UploadMovieComponent = () => {
  const { register, handleSubmit } = useForm();
  const [result, setResult] = useState("");
  const movies = useRecoilValue(movieState);
  const setRequestData = useSetRecoilState(videoUploadState);

  return (
    <div>
      <Form
        onSubmit={handleSubmit((data) =>
          setRequestData({
            id: data.id,
            file: data.movieFile[0],
          })
        )}
      >
        <Form.Group controlId="exampleForm.SelectCustom">
          <Form.Label>Select movie</Form.Label>
          <Form.Control as="select" ref={register} name="id" custom>
            {movies.map((movie) => (
              <option value={movie.id}>{movie.title}</option>
            ))}
          </Form.Control>
        </Form.Group>
        <Form.Group>
          <Form.File name="movieFile" ref={register} />
        </Form.Group>
        <Button type="submit" variant="outline-warning">
          Upload
        </Button>
      </Form>
      <CineburProgressBar />
    </div>
  );
};

/**
* Firestore setup
*/

firebase.initializeApp(firebaseConfig);

export const firestoreState = atom({
  key: "firestore", // unique ID (with respect to other atoms/selectors)
  default: firebase.app().firestore(), // default value (aka initial value)
});

export const firestoreStorageState = atom({
  key: "firestoreStorage",
  default: firebase.storage(),
});

/**
* Recoil states
*/

export const movieState = selector({
  key: "movieState",
  get: async ({ get }) => {
    const firestore = get(firestoreState);
    const movies = await firestore.collection("movies").get();
    const movieNames = [...movies.docs].map((doc) => {
      const movieData = doc.data();
      return {
        id: doc.id,
        title: movieData.title,
        assetRef: movieData.assetRef,
      };
    });
    return movieNames;
  },
});

/**
 * Monitor video uploading
 */

export const videoUploadProgressState = atom<firebase.storage.UploadTask>({
  key: "videoProgress",
  default: null,
});

export const videoUploadState = selector({
  key: "videoUploadState",
  get: async ({ get }) => {
    return "";
  },
  set: async ({ set, get }, data: { [key: string]: any }) => {
    const ref = `movies/${data.id}/video/${data.file.name}`;
    const storage = get(firestoreStorageState);
    const task = storage.ref(ref).put(data.file);
    set(videoUploadProgressState, task);
    const upload = await task;
    const response = await fetch(
      "http://*******:**/streaming/create-video-asset",
      {
        method: "post",
        headers: {
          "Content-type": "application/json",
        },
        body: JSON.stringify({ url: await upload.ref.getDownloadURL() }),
      }
    );
    const firestore = get(firestoreState);

    const movieDoc = firestore.doc(`movies/${data.id}`);
    const result = await response.json();
    movieDoc.update({ assetRef: result.playback_id });
    return result.playback_id;
  },
});



推荐阅读