I'm trying to get into using react recoil
for state management and I'm loving it so far, however at the moment I'm trying to integrate it with react-hook-form
and I can't quite find a setup that works for me.
It's simple, I want to execute a function that uploads a file onSubmit
after completing a Form
. I've setup a selector
to make an async request and update a corresponding atom
with the upload progress, but I'm having trouble executing the function as it is getting called within a callback rather than a functional component, I've tried to extract it into a custom hook but that wasn't effective either; I've provided the code below, if anyone has any ideas let me know!
Example error of the current implementation:
React Hook "useRecoilValue" is called in function "submit" which is neither a React function component or a custom React Hook function
Code:
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>
);
}
the solution involved using the
set
property on the selector to pass in adata
object containing the parameters I want to sent to theurl
.This way you can use the
useSetRecoilState
hook to obtain a callback that can be used outside the the scope of the component tree: