Replies: 28 comments 33 replies
-
Just hit this myself for the exact same use case -- I have a modal, the user gets errors on submit and hits cancel. When they re-open the modal the I want |
Beta Was this translation helpful? Give feedback.
-
Running into a similar issue with stale data in the render function causing this wonkiness: I don't understand. What's even useful about previous data when I'm calling I tried wrapping Has anyone figured out a clean solution that doesn't require managing data in multiple places? (and hey @WesleyKapow 👋 … small world!) |
Beta Was this translation helpful? Give feedback.
-
Yeah...would love this. Generally people have suggested workarounds like moving the data fetching further down the component tree but that tends to either break container/display-component patterns or make them unwieldy like |
Beta Was this translation helpful? Give feedback.
-
The nice thing about using the fetcher is being able to directly link a simple component's UI to the https://stackblitz.com/edit/node-9ta3ig?file=app/routes/index.tsx Here's a simplified version of a form I made today that brought this up for me. It's a 2 phase contact signup + verification form, where state from the previous submit is round-tripped in order to activate (and be submitted with) the 2nd phase of the form. This could be done with 2 forms/fetchers instead, and it might be more clear that way, but even in that case, if the visibility of the 2nd form/phase is dependent on the |
Beta Was this translation helpful? Give feedback.
-
is there any workaround to reset fetcher ? |
Beta Was this translation helpful? Give feedback.
-
I investigated what it would take to implement this and it needs to be done within react-router-dom since remix creates a wrapper around that. I have added a feature request here remix-run/react-router#10231. I was able to work around this by mounting and unmounting the component based on what I was using for a visible state. I don't think this is optimal but it gets the job done. I was inspired by the route-modal example which may also solve this issue, at least for now. |
Beta Was this translation helpful? Give feedback.
-
Another workaround I've used is to not use the value in const [myData, setMyData] = useState();
useEffect(() => {
if (fetcher.state === "submitting" || fetcher.state === "loading") {
setMyData(undefined);
} else if (fetcher.state === "idle") {
setMyData(fetcher.data?.myData);
}
}, [fetcher.state, fetcher.data]);
// Use `myData` instead of `fetcher.data.myData` Kind of ugly but it works if you don't have too much data to worry about and don't want to create a reset route for whatever reason. |
Beta Was this translation helpful? Give feedback.
-
You could also reload the page to reset the action data (and clean it from previous validation error fields)... const navigate = useNavigate();
const location = useLocation();
...
<button
className="ml-auto"
onClick={() => {
setIsEditing(false);
// -> to reset potential action validation errors
navigate(location.pathname);
}}
>
Annuler
</button> |
Beta Was this translation helpful? Give feedback.
-
Here's a working fetcher with export type FetcherWithComponentsReset<T> = FetcherWithComponents<T> & {
reset: () => void;
};
export function useFetcherWithReset<T>(): FetcherWithComponentsReset<T> {
const fetcher = useFetcher<T>();
const [data, setData] = useState(fetcher.data);
useEffect(() => {
if (fetcher.state === "idle") {
setData(fetcher.data);
}
}, [fetcher.state, fetcher.data]);
return {
...fetcher,
data: data as T,
reset: () => setData(undefined),
};
} ⚡️ StackBlitz https://stackblitz.com/edit/remix-run-remix-vhgfqt?file=app%2Froutes%2F_index.tsx |
Beta Was this translation helpful? Give feedback.
-
Not much related, but seems usefull to share... Instead of reseting it, I trust in the last "loading" state and wrap the fetcher in a global Provider, all around my application, then fetch through a hook. The loading state of the fetcher has a last "handshake" step, where it asks for the data chunk while in loading. So I never trust in iddle fetchers, but in the last step of the handshake. (loading + data chunk) The state transition on the fetcher works so it gets the data from the fetcher in "loading" state still, and considers it done after that ( iddle ). So you get a mirrored result on fetcher.data. One for loading with data and the first one for iddle with data. Whenever you have a data chunk in "loading" state, means that it is the last handshake of loading state. |
Beta Was this translation helpful? Give feedback.
-
I think with the new |
Beta Was this translation helpful? Give feedback.
-
Having the same problem while implementing a toast message: 1 - Delete an item Maybe using a different fetcher key would solve the problem but I need the key to be the same so I can show some loading state in a component separated from the route and the toast itself |
Beta Was this translation helpful? Give feedback.
-
btw why doesn't the fetcher resets its data when the route change? |
Beta Was this translation helpful? Give feedback.
-
Modified @kiliman's solution with a fetcher key. https://gist.github.com/arunmmanoharan/38d313f28dc17637a0e3cfa8c6205bd5 |
Beta Was this translation helpful? Give feedback.
-
You can also use a useState value for the useFetcher key. Resetting this value to a new one also resets the fetcher, since it listens to the actual state of the value. See my example code: function ExampleComponent() {
const [key, setKey] = useState("a123");
const fetcher = useFetcher({ key });
const [showModal, setShowModal] = useState(false);
const [dataChanged, setDataChanged] = useState(false);
function handleShowModal() {
setShowModal(true);
}
function handleCloseModal() {
setShowModal(false);
}
useEffect(() => {
if (fetcher.state === "idle" && fetcher.data?.ok && !!fetcher.data?.msg && !dataChanged && showModal) {
setDataChanged(true);
}
}, [dataChanged, showModal, fetcher]);
useEffect(() => {
if (dataChanged && !showModal) {
setKey("b321");
}
}, [dataChanged, showModal]);
useEffect(() => {
setDataChanged(false);
}, [key]);
return (
<div>
<button type="button" onClick={handleShowModal}>Show Modal</button>
<Modal show={showModal} onClose={handleCloseModal}>
<>
{dataChanged ? (
<div>Data has been changed</div>
) : (
null
)}
<fetcher.Form method="post">
<input type="email" name="email" />
{!dataChanged ? (
<button type="submit">Change E-Mail</button>
): (
<button type="button" size="small" onClick={handleCloseModal}>Close Modal</button>
)}
</fetcher.Form>
</>
</Modal>
</div>
);
} |
Beta Was this translation helpful? Give feedback.
-
What is the actual overhead of using random keys and with that creating new fetchers all over the place? Say, I have a message board where people use an editor to create or edit posts. Also let's say, the editor uses useFetcher({ key: 'edit-' + Date().toISOString()}) which over the lifetime of a session could create dozens if not hundreds of fetchers in that client. Is this even noticeable? Greets |
Beta Was this translation helpful? Give feedback.
-
The Link component you can provide a reloadDocument prop which will reset the state of fetcher. fetcher.data will be undefined in this case. Hope this helps anyone running into this issue. |
Beta Was this translation helpful? Give feedback.
-
I thought I needed a way to reset the fetcher, but adding more entropy to the key fixed it. A param did the job in this case, just go to town with that key. Hope this helps anyone. const params = useParams()
const fetcher = useTypedFetcher<typeof fragmentLoader>({
key: `filter-${cat}-${params.urlId}`,
}) |
Beta Was this translation helpful? Give feedback.
-
The reset via key trick works but having a native |
Beta Was this translation helpful? Give feedback.
-
Just hit a wall where I want a modal to close when the fetcher data submits successfully but not when it errors, and as the state persists I can’t open it ever again 😆 @ryanflorence pls |
Beta Was this translation helpful? Give feedback.
-
I think there’s a baseline concept here that’s getting missed. The idea
with a form fetcher is to link an html form to some remote endpoint. The
thing getting missed is that the native form has a reset(), but the fetcher
doesn’t. It’s a missing feature. Someone asserted here that the point is
just to update local state and that it shouldn’t be used directly. If so
then this requires extra useState or useEffect magic which Ryan has claimed
is his goal to avoid. It seems like such a small and obvious thing I’m
actually quite surprised there’s so much debate about it.
T
…On Mon, Oct 14, 2024 at 8:22 AM ✦ freddie ***@***.***> wrote:
Just hit a wall where I want a modal to close when the fetcher data
submits successfully but not when it errors, and as the state persists I
can’t open it ever again 😆
@ryanflorence <https://github.com/ryanflorence> pls
—
Reply to this email directly, view it on GitHub
<#2749 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAEXVZV2FALZQSXIGNIGRB3Z3POU3AVCNFSM5WZ73QGKU5DIOJSWCZC7NNSXTOSENFZWG5LTONUW63SDN5WW2ZLOOQ5TCMBZGM4DGNZY>
.
You are receiving this because you commented.Message ID:
***@***.***>
|
Beta Was this translation helpful? Give feedback.
-
I would really like this feature as well. There shouldn't be a debate about this imo. |
Beta Was this translation helpful? Give feedback.
-
This works for me: import { useCallback, useMemo, useState } from 'react';
import { useFetcher } from '@remix-run/react';
export function useResettableFetcher<T>() {
const [fetcherKey, setFetcherKey] = useState(Math.random().toString());
const fetcher = useFetcher<T>({ key: fetcherKey });
const resetFetcher = useCallback(() => setFetcherKey(Math.random().toString()), []);
return useMemo(() => [fetcher, resetFetcher] as const, [fetcher, resetFetcher]);
} EDIT: replaced lodash's |
Beta Was this translation helpful? Give feedback.
-
I don't get why no one is using or suggested calling |
Beta Was this translation helpful? Give feedback.
-
typescript friendly version. import { useFetcher } from '@remix-run/react'
import type { FetcherWithComponents } from '@remix-run/react'
import { useEffect, useState } from 'react'
export type FetcherWithComponentsReset<T> = Omit<
FetcherWithComponents<T>,
'data'
> & {
data: T | undefined
reset: () => void
}
export default function useFetcherWithReset<
T,
>(): FetcherWithComponentsReset<T> {
const fetcher = useFetcher<T>() as unknown as FetcherWithComponents<T>
const [data, setData] = useState<T | undefined>(fetcher.data)
useEffect(() => {
if (fetcher.state === 'idle') {
setData(fetcher.data)
}
}, [fetcher.state, fetcher.data])
return {
...fetcher,
data,
reset: () => setData(undefined),
}
} reference: #2749 (comment) |
Beta Was this translation helpful? Give feedback.
-
React router version of hook with key support by arunmmanoharan as described at #2749 (comment). import { useEffect, useState } from "react";
import type { FetcherWithComponents } from "react-router";
import { useFetcher } from "react-router";
/**
* A higher-order function that creates a new FetcherWithComponentsReset
* instance, which extends the FetcherWithComponents interface. The new
* instance includes an additional method `reset` that can be used to reset
* the state of the fetcher.
*
* @template T - The type of data returned by the fetcher.
* @param fetcherWithComponents - The FetcherWithComponents instance to be extended.
* @returns A new FetcherWithComponentsReset instance.
*/
export type FetcherWithComponentsReset<T> = FetcherWithComponents<T> & {
reset: () => void;
};
/**
* Custom hook that wraps the useFetcher hook with the ability to reset data.
*
* @param {Object} opts - Optional options to pass to the useFetcher hook.
* @returns {Object} - An object containing fetcher properties with added reset functionality.
*/
export function useFetcherWithReset<T = unknown>(
opts?: Parameters<typeof useFetcher>[0],
): FetcherWithComponentsReset<T> {
const fetcher = useFetcher<T>(opts);
const [data, setData] = useState(fetcher.data);
useEffect(() => {
if (fetcher.state === "idle") {
setData(fetcher.data);
}
}, [fetcher.state, fetcher.data]);
return {
...fetcher,
data: data as T,
reset: () => setData(undefined),
};
} |
Beta Was this translation helpful? Give feedback.
-
The problem with the
|
Beta Was this translation helpful? Give feedback.
-
Thank you for opening this discussion, and our apologies; we haven't gotten around to it yet! With the release of React Router v7 we are sun-setting continued development/maintenance on Remix v2. If you have not already upgraded to React Router v7, we recommend you do so. We've tried to make the upgrade process as smooth as possible with our Future Flags. We are now in the process of cleaning up outdated issues and pull requests to improve the overall hygiene of our repositories. We plan to continue to address two types of issues in Remix v2:
If you believe this issue meets one of those criteria, please respond or create a new issue. For all other issues, ongoing maintenance will be happening in React Router v7, so:
If you have any questions, you can always reach out on Discord. Thanks again for providing feedback and helping us make our framework even better! |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
What is the new or updated feature that you are suggesting?
Reset the fetcher data returned from the action? Using something Like
fetcher.reset()
Why should this feature be included?
Example Flow would be
fetcher?.data?.formError
from the actionthe user clicks on dismiss error
or something similar, I want to reset fetcher data returned from action.Yeah there can be a workaround like
fetcher.type === "done" && fetcher.data.error
then set a client-side state withfetcher?.data?.formError
and use this state instead offetcher?.data?.formError
in UI. Now we have full control over the error message.But doing this manual work in every component is a little annoying.
https://www.loom.com/share/f20262a4773d460c9f4d962cc8880431
In the video,
Here If I could do like
fetcher.reset()
on closing the modal, that would be awesome. [Though I have no idea how hard is it to implement or even if it possible to implement this 😅]Let me know if something awesome exists that solves this problem without needing to maintain a state on the client side. 🙇
Beta Was this translation helpful? Give feedback.
All reactions