Remix's useFetcher
doesn't return a Promise for any of its methods (like fetcher.submit()
) because Remix doesn't want you to explicitly await
anything so they can handle things like cancellation for you. Instead, they recommend adding a useEffect
and performing whatever logic you need to after the fetcher is in a particular state.
I found using an effect to run some logic after a submission to be too indirect, and there seem to be plenty of cases where you want to submit a form and then perform some other work on the client (sometimes async, like requesting the user's permission for their location), and I'd rather just do that after a submission in the event handler rather than an effect.
So here's a proof of concept hook that wraps Remix's useFetcher
and returns a version of submit
that is a promise, and resolves with the data from the action:
function useFetcherWithPromise() {
let resolveRef = useRef();
let promiseRef = useRef();
if (!promiseRef.current) {
promiseRef.current = new Promise((resolve) => {
resolveRef.current = resolve;
});
}
let fetcher = useFetcher();
async function submit(...args) {
fetcher.submit(...args);
return promiseRef.current;
}
useEffect(() => {
if (fetcher.data) {
resolveRef.current(fetcher.data);
}
}, [fetcher]);
return { ...fetcher, submit };
}
Now I can use it like this:
<fetcher.Form
onSubmit={handleSubmit}
className="mt-4 space-y-4"
method="post"
>
<!-- form -->
</fetcher.Form>
and the event handler:
async function handleSubmit(event) {
event.preventDefault();
let data = await fetcher.submit(event.target, { method: "post" });
// do additional work
return navigate(`/exercises/${data.exerciseId}`);
}
Know that this subjects you to some pitfalls that Remix's Form is designed to protect you from, but again it seems to me there are plenty of cases where you'd want to drop down to this level.
thanks for sharing, this helped with a requirement for an asynchronous wait I required on
fetcher.submit
I came across one gotcha though, where once resolved subsequent submit requests would auto-resolve, due to the first call.
In order to fix this, I tweaked the hook above, now everytime a promise it resolves, it resets the resolver for future requests.
Hope this helps!