API for hooking into useFetcher
lifecycle. Ex: useFetcher({onSuccess, onError, ...})
#10013
Replies: 15 comments 11 replies
-
Thanks for filing @cliffordfajardo! I'm going to transfer this to the react router repo since that's where |
Beta Was this translation helpful? Give feedback.
-
Hi! I would like to add a point of discussion to this proposal. First of all I think it is awesome and sincerely welcome to add those callbacks to fetchers since they would make many situations more straightforward, but callbacks come with their own downsides when they start nesting. In the current iteration of I understand that promisifying const promisifiedSubmission = async () => {
try {
onStart()
const { error, data } = await submit({ param: 'value' }, { action: '/some/action' })
if (error) {
onError(error)
} else {
onSuccess(data)
}
onComplete()
} catch (e) {
onNastyUnexpectedError(e)
} finally {
onFinallyCompleted()
}
} Of course this is just one example of what it can be mapped to, but I think it is already clear that promises bring much more control into the request flow than callbacks. const promisifiedMultiSubmission = async () => {
try {
onStart()
const result1 = await submit({ param: 'value' }, { action: '/some/action' })
if (result1.error) {
onFirstError(result1.error)
return onComplete()
}
const result2 = await submit(result1.data, { action: '/some/other/action' })
if (result2.error) {
onSecondError(result2.error)
} else {
onSuccess(result2.data)
}
onComplete()
} catch (e) {
onNastyUnexpectedError(e)
} finally {
onFinallyCompleted()
}
} Anyway I just wanted to add another point of view to a very welcome proposal, IMHO when working with data transfers the more flexibility we have as developers the simpler it is to adopt a new library/framework. (as of now the simplicity of |
Beta Was this translation helpful? Give feedback.
-
I've got a half proposal written up for this exact thing that I haven't published. I'm on board. Anything to avoid another |
Beta Was this translation helpful? Give feedback.
-
It's not possible to use Async |
Beta Was this translation helpful? Give feedback.
-
Related proposal over in Remix: remix-run/remix#4645 |
Beta Was this translation helpful? Give feedback.
-
I have no solution to the following. Sometimes, when you do this: export function TopComponent() {
const myDeeplyNestingFetcher = useFetcher();
...
return (
<C1>
<C2 fetcher={myDeeplyNestingFetcher} />
<C3 fetcher={myDeeplyNestingFetcher} />
</C1>
);
} and this: export function C2 ({ fetcher, ...rest }: { fetcher: FetcherWithComponents<any>; ...rest }) {
const [state1, setState1] = React.useState('');
const [state2, setState2] = React.useState(0);
return (
<div>
<...stuffWithState>
<button onClick={(e) => fetcher.submit({ e.currentTarget.value }, { action: 'x', method: 'post' })}>Submit</button>;
</div>
);
} you could end up in a situation where you sorely wish you could add these callbacks to the fetcher invocation itself, i.e, fetcher.submit({ e.currentTarget.value }, { action: 'x', method: 'post', onSuccess: () => 'So close to the action!' });
<fetcher.Form ... onSuccess={() => 'Still so close to the action!'}><...stuff></fetcher.Form>; instead of only being able to do it up top at the level of the hook's invocation, i.e., const fetcher = useFetcher({ onSuccess: () => "That's a long way down!" }); I don't know if the other proposal about promises could help here, and I suppose there may be ways to code around this, but I'm not sure that's always realistic. Sometimes you can't redesign your architecture because it's "done." And no one wants to hear it anyway. And your ticket's gotta get cleared. Well, in that unspoken, but sometimes realistic scenario, I can imagine this level of flexibility could make someone's day. |
Beta Was this translation helpful? Give feedback.
-
Revalidator might also benefit from this treatment. The one specific use case is when the network is down temporarily, calling revalidate will result in a hard crash (can't fetch chunk error) that can only be caught at the root route. Pretty intense. Would be great to at least be able to locally handle that category of errors, where revalidate() never even made it back to the loaders at all. |
Beta Was this translation helpful? Give feedback.
-
I want to be able to refresh my loader at an interval. I tried using useRevalidator and useFetcher with intervals, but both throw a whole error when the network is down (every time I open my laptop with the app still open). Here is the somewhat hacky solution I'm using for now. I'd welcome any suggestions for simplifying it: https://gist.github.com/dctanner/882a71fbb6f15153108cdfdf5f33c545 |
Beta Was this translation helpful? Give feedback.
-
Hi, this is a direct copy of this since here might be the more appropriate place. Running
My work around is
I expect to be able to handle errors gracefully, but, the |
Beta Was this translation helpful? Give feedback.
-
@cliffordfajardo do you know if there's any progress for this proposal? |
Beta Was this translation helpful? Give feedback.
-
The current remix error handling is totally unacceptable. I guess I'll just integrate with react-query until whatever one of these proposals are implemented. |
Beta Was this translation helpful? Give feedback.
-
In most situations, you can use But I think there one case where such workaround isn't very useful: tracking an analytics event after a successful submission that redirects to another route. I mean, redirecting means the |
Beta Was this translation helpful? Give feedback.
-
In case it helps anyone, you can use a clientLoader to work around this as follows: export const clientLoader = async ({
serverLoader,
}: ClientLoaderFunctionArgs) => {
try {
return await serverLoader();
} catch (e) {
// handle error, return fallback etc.
}
}; |
Beta Was this translation helpful? Give feedback.
-
This is the solution I ended up for https://glama.ai import { usePrevious } from './usePrevious';
import { useEffect, useRef } from 'react';
const usePrevious = (value: unknown) => {
const ref = useRef<unknown>();
useEffect(() => {
ref.current = value;
});
return ref.current;
};
const useOnSubmit = ({
state,
onSubmit,
}: {
onSubmit: () => void;
state: 'submitting' | 'idle' | 'loading';
}) => {
const submitting = state === 'submitting';
const previousSubmitting = usePrevious(submitting);
useEffect(() => {
if (previousSubmitting && !submitting) {
onSubmit();
}
}, [previousSubmitting, submitting]);
}; Then just use it like this: const fetcher = useFetcher<typeof action>();
useOnSubmit({
onSubmit: () => {
// your logic
},
state: fetcher.state,
}); |
Beta Was this translation helpful? Give feedback.
-
For anyone interested, I created a Usageconst { submit, isLoading } = useAsyncFetcherSubmit()
...
await submit({ data: 'yep' }, { method: 'POST', action: '/api/path' })
...
<button disabled={isLoading} /> Implementationimport { useFetcher } from '@remix-run/react'
import { useState, useCallback, useEffect } from 'react'
export const useAsyncFetcherSubmit = () => {
const fetcher = useFetcher()
const [isLoading, setIsLoading] = useState(false)
const [pendingPromise, setPendingPromise] = useState<{
resolve: () => void
reject: (error: any) => void
} | null>(null)
const submit = useCallback(
(
submitData: Parameters<typeof fetcher.submit>[0],
options?: Parameters<typeof fetcher.submit>[1],
) => {
return new Promise<void>((resolve, reject) => {
fetcher.submit(submitData, options)
setIsLoading(true)
setPendingPromise({ resolve, reject })
})
},
[fetcher],
)
useEffect(() => {
if (fetcher.state === 'idle' && pendingPromise) {
setIsLoading(false)
pendingPromise.resolve()
setPendingPromise(null)
}
}, [fetcher.state, pendingPromise])
return { submit, isLoading, fetcher }
} |
Beta Was this translation helpful? Give feedback.
-
What is the Proposal?
It would be helpful to be to hook into the event lifecycle of useFetcher & useSubmit hooks like this:
What is the current challenge?
React.useEffect
& clutter your component or write a hook abstraction overuseSubmit
&useFetcher
react-query
,apollo-client
,swr
or similar react data fetching libs & relying on these lifecycle hooks for your UI experienceUse Cases
react-query
,apollo-client
,swr
or similar react data fetching libs in a client side react app & you are trying to migrate to remix, you will run into the problem of not having access to lifecycle hooks likeonSuccess, onError
for your API calls, which are commonly used for showing success & error notifications in the UI.useSubmit
&useFetcher
What I've Tried:
I've created my own wrapper around
useSubmit
&useFetcher
& currently need to maintain the code below. The code is error prone IMO because if Remix changes its APIs, my wrapper code would likely break.References
Beta Was this translation helpful? Give feedback.
All reactions