From 4b27585f2f4bff04478fabe24f8ea6c2142d8e16 Mon Sep 17 00:00:00 2001 From: Fabien BERNARD Date: Thu, 13 Sep 2018 18:05:48 +0200 Subject: [PATCH] Add a retry on error --- src/Context.tsx | 8 ++++++-- src/Get.test.tsx | 43 ++++++++++++++++++++++++++++++++++++++---- src/Get.tsx | 2 +- src/Mutate.test.tsx | 46 ++++++++++++++++++++++++++++++++++++++++++--- src/Mutate.tsx | 2 +- 5 files changed, 90 insertions(+), 11 deletions(-) diff --git a/src/Context.tsx b/src/Context.tsx index ec4f6f8a..f254401f 100644 --- a/src/Context.tsx +++ b/src/Context.tsx @@ -15,9 +15,13 @@ export interface RestfulReactProviderProps { */ requestOptions?: (() => Partial) | Partial; /** - * Trigger on each error + * Trigger on each error. + * For `Get` and `Mutation` calls, you can also call `retry` to retry the exact same request. + * Please note that it's quite hard to retrieve the response data after a retry mutation in this case. + * Depending of your case, it can be easier to add a `localErrorOnly` on your `Mutate` component + * to deal with your retry locally instead of in the provider scope. */ - onError?: (err: any) => void; + onError?: (err: any, retry?: () => Promise) => void; } const { Provider, Consumer: RestfulReactConsumer } = React.createContext>({ diff --git a/src/Get.test.tsx b/src/Get.test.tsx index 4af3ec9c..8c0eb070 100644 --- a/src/Get.test.tsx +++ b/src/Get.test.tsx @@ -169,10 +169,45 @@ describe("Get", () => { ); await wait(() => expect(children.mock.calls.length).toBe(2)); - expect(onError).toBeCalledWith({ - data: { message: "You shall not pass!" }, - message: "Failed to fetch: 401 Unauthorized", - }); + expect(onError).toBeCalledWith( + { + data: { message: "You shall not pass!" }, + message: "Failed to fetch: 401 Unauthorized", + }, + expect.any(Function), + ); + }); + + it("should be able to retry after an error", async () => { + nock("https://my-awesome-api.fake") + .get("/") + .reply(401, { message: "You shall not pass!" }); + nock("https://my-awesome-api.fake") + .get("/") + .reply(200, { message: "You shall pass :)" }); + + const children = jest.fn(); + children.mockReturnValue(
); + + const onError = jest.fn(); + + render( + + {children} + , + ); + + await wait(() => expect(children.mock.calls.length).toBe(2)); + expect(onError).toBeCalledWith( + { + data: { message: "You shall not pass!" }, + message: "Failed to fetch: 401 Unauthorized", + }, + expect.any(Function), + ); + onError.mock.calls[0][1](); + await wait(() => expect(children.mock.calls.length).toBe(4)); + expect(children.mock.calls[3][0]).toEqual({ message: "You shall pass :)" }); }); it("should not call the provider onError if localErrorOnly is true", async () => { diff --git a/src/Get.tsx b/src/Get.tsx index b41e34f0..b1cb4816 100644 --- a/src/Get.tsx +++ b/src/Get.tsx @@ -214,7 +214,7 @@ class ContextlessGet extends React.Component< }); if (!this.props.localErrorOnly && this.props.onError) { - this.props.onError(error); + this.props.onError(error, () => this.fetch(requestPath, thisRequestOptions)); } return null; diff --git a/src/Mutate.test.tsx b/src/Mutate.test.tsx index 24784cf2..442367e9 100644 --- a/src/Mutate.test.tsx +++ b/src/Mutate.test.tsx @@ -220,10 +220,50 @@ describe("Mutate", () => { /* noop */ }); - expect(onError).toBeCalledWith({ - data: { message: "You shall not pass!" }, - message: "Failed to fetch: 401 Unauthorized", + expect(onError).toBeCalledWith( + { + data: { message: "You shall not pass!" }, + message: "Failed to fetch: 401 Unauthorized", + }, + expect.any(Function), + ); + }); + + it("should be able to retry after an error", async () => { + nock("https://my-awesome-api.fake") + .post("/") + .reply(401, { message: "You shall not pass!" }); + nock("https://my-awesome-api.fake") + .post("/") + .reply(200, { message: "You shall pass :)" }); + + const children = jest.fn(); + children.mockReturnValue(
); + + const onError = jest.fn(); + + render( + + + {children} + + , + ); + + // post action + await children.mock.calls[0][0]().catch(() => { + /* noop */ }); + + expect(onError).toBeCalledWith( + { + data: { message: "You shall not pass!" }, + message: "Failed to fetch: 401 Unauthorized", + }, + expect.any(Function), + ); + const data = await onError.mock.calls[0][1](); + expect(data).toEqual({ message: "You shall pass :)" }); }); it("should not call the provider onError if localErrorOnly is true", async () => { diff --git a/src/Mutate.tsx b/src/Mutate.tsx index 85facacc..356a65c4 100644 --- a/src/Mutate.tsx +++ b/src/Mutate.tsx @@ -140,7 +140,7 @@ class ContextlessMutate extends React.Component< }); if (!this.props.localErrorOnly && this.props.onError) { - this.props.onError(error); + this.props.onError(error, () => this.mutate(body, mutateRequestOptions)); } throw error;