From c6267df2d312fed734cd85ed30a2a6fae3ae5044 Mon Sep 17 00:00:00 2001 From: Niels Vleeming Date: Thu, 23 Jun 2022 13:02:07 +0200 Subject: [PATCH 1/2] Add function to reset a fetcher --- integration/fetcher-test.ts | 18 ++++++- .../remix-react/__tests__/transition-test.tsx | 48 +++++++++++++++++++ packages/remix-react/components.tsx | 4 ++ packages/remix-react/transition.ts | 10 ++++ 4 files changed, 79 insertions(+), 1 deletion(-) diff --git a/integration/fetcher-test.ts b/integration/fetcher-test.ts index 950a8ee758b..bc5428be986 100644 --- a/integration/fetcher-test.ts +++ b/integration/fetcher-test.ts @@ -10,6 +10,7 @@ test.describe("useFetcher", () => { let CHEESESTEAK = "CHEESESTEAK"; let LUNCH = "LUNCH"; + let EMPTY_PLATE = "EMPTY_PLATE"; test.beforeAll(async () => { fixture = await createFixture({ @@ -75,7 +76,12 @@ test.describe("useFetcher", () => { }}> submit -
{fetcher.data}
+ +
{fetcher.data != null ? fetcher.data : "${EMPTY_PLATE}"}
); } @@ -123,10 +129,20 @@ test.describe("useFetcher", () => { }); test("load can hit a loader", async ({ page }) => { + let app = new PlaywrightFixture(appFixture, page); + await app.goto("/"); + expect(await app.getHtml("pre")).toMatch(EMPTY_PLATE); + await app.clickElement("#fetcher-load"); + expect(await app.getHtml("pre")).toMatch(LUNCH); + }); + + test("fetcher can be reset", async ({ page }) => { let app = new PlaywrightFixture(appFixture, page); await app.goto("/"); await app.clickElement("#fetcher-load"); expect(await app.getHtml("pre")).toMatch(LUNCH); + await app.clickElement("#fetcher-reset"); + expect(await app.getHtml("pre")).toMatch(EMPTY_PLATE); }); test("submit can hit an action", async ({ page }) => { diff --git a/packages/remix-react/__tests__/transition-test.tsx b/packages/remix-react/__tests__/transition-test.tsx index c4a37e78b3a..20f7393064c 100644 --- a/packages/remix-react/__tests__/transition-test.tsx +++ b/packages/remix-react/__tests__/transition-test.tsx @@ -1260,6 +1260,45 @@ describe("fetcher states", () => { expect(fetcher.data).toBe("B DATA"); }); + test("loader reset while loading", async () => { + let t = setup({ url: "/foo" }); + + let A = t.fetch.get("/foo"); + let fetcher = t.getFetcher(A.key); + expect(fetcher.state).toBe("loading"); + expect(fetcher.type).toBe("normalLoad"); + + t.resetFetcher(A.key); + await A.loader.resolve("A DATA"); + + fetcher = t.getFetcher(A.key); + expect(fetcher.state).toBe("idle"); + expect(fetcher.type).toBe("init"); + expect(fetcher.data).toBe(undefined); + }); + + test("loader reset after load", async () => { + let t = setup({ url: "/foo" }); + + let A = t.fetch.get("/foo"); + let fetcher = t.getFetcher(A.key); + expect(fetcher.state).toBe("loading"); + expect(fetcher.type).toBe("normalLoad"); + + await A.loader.resolve("A DATA"); + fetcher = t.getFetcher(A.key); + expect(fetcher.state).toBe("idle"); + expect(fetcher.type).toBe("done"); + expect(fetcher.data).toBe("A DATA"); + + t.resetFetcher(A.key); + + fetcher = t.getFetcher(A.key); + expect(fetcher.state).toBe("idle"); + expect(fetcher.type).toBe("init"); + expect(fetcher.data).toBe(undefined); + }); + test("loader submission fetch", async () => { let t = setup({ url: "/foo" }); @@ -1507,6 +1546,14 @@ describe("fetcher resubmissions/re-gets", () => { expect(B.loader.abortMock.calls.length).toBe(1); }); + it("aborts on reset", async () => { + let t = setup(); + let key = "KEY"; + let A = t.fetch.submitGet("/foo", key); + t.tm.resetFetcher(key); + expect(A.loader.abortMock.calls.length).toBe(1); + }); + it("aborts resubmissions action call", async () => { let t = setup(); let key = "KEY"; @@ -2276,6 +2323,7 @@ let setup = ({ url } = { url: "/" }) => { fetch, getState: tm.getState, getFetcher: tm.getFetcher, + resetFetcher: tm.resetFetcher, handleChange, handleRedirect, rootLoaderMock: rootLoader.mock, diff --git a/packages/remix-react/components.tsx b/packages/remix-react/components.tsx index 721e5a49ae3..49534cf36d5 100644 --- a/packages/remix-react/components.tsx +++ b/packages/remix-react/components.tsx @@ -1382,6 +1382,9 @@ export function useFetcher(): FetcherWithComponents { let [load] = React.useState(() => (href: string) => { transitionManager.send({ type: "fetcher", href, key }); }); + let [reset] = React.useState(() => () => { + transitionManager.resetFetcher(key); + }); let submit = useSubmitImpl(key); let fetcher = transitionManager.getFetcher(key); @@ -1391,6 +1394,7 @@ export function useFetcher(): FetcherWithComponents { Form, submit, load, + reset, ...fetcher, }), [fetcher, Form, submit, load] diff --git a/packages/remix-react/transition.ts b/packages/remix-react/transition.ts index 346fd5d7637..65b7542f1b0 100644 --- a/packages/remix-react/transition.ts +++ b/packages/remix-react/transition.ts @@ -1369,6 +1369,15 @@ export function createTransitionManager(init: TransitionManagerInit) { fetchControllers.delete(key); } + function resetFetcher(key: string) { + console.debug(`[transition] resetting fetcher (key: ${key})`); + let fetcher = getFetcher(key); + invariant(fetcher, `Expected fetcher: ${key}`); + if (fetchControllers.has(key)) abortFetcher(key); + setFetcher(key, IDLE_FETCHER); + update({ fetchers: new Map(state.fetchers) }); + } + function markFetchRedirectsDone(): void { let doneKeys = []; for (let key of fetchRedirectIds) { @@ -1387,6 +1396,7 @@ export function createTransitionManager(init: TransitionManagerInit) { getState, getFetcher, deleteFetcher, + resetFetcher, dispose, get _internalFetchControllers() { return fetchControllers; From a229507a90341936890c930362c41424d1cb584b Mon Sep 17 00:00:00 2001 From: Niels Vleeming Date: Thu, 23 Jun 2022 16:54:27 +0200 Subject: [PATCH 2/2] Sign CLA --- contributors.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/contributors.yml b/contributors.yml index b736d052c1b..6298b654305 100644 --- a/contributors.yml +++ b/contributors.yml @@ -363,6 +363,7 @@ - vimutti77 - visormatt - vkrol +- Vlemert - vlindhol - weavdale - wKovacs64