Skip to content
This repository has been archived by the owner on Nov 11, 2023. It is now read-only.

Commit

Permalink
Add a retry on error
Browse files Browse the repository at this point in the history
  • Loading branch information
fabien0102 committed Sep 13, 2018
1 parent 1fde80b commit 4b27585
Show file tree
Hide file tree
Showing 5 changed files with 90 additions and 11 deletions.
8 changes: 6 additions & 2 deletions src/Context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,13 @@ export interface RestfulReactProviderProps<T = any> {
*/
requestOptions?: (() => Partial<RequestInit>) | Partial<RequestInit>;
/**
* 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<T | null>) => void;
}

const { Provider, Consumer: RestfulReactConsumer } = React.createContext<Required<RestfulReactProviderProps>>({
Expand Down
43 changes: 39 additions & 4 deletions src/Get.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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(<div />);

const onError = jest.fn();

render(
<RestfulProvider base="https://my-awesome-api.fake" onError={onError}>
<Get path="">{children}</Get>
</RestfulProvider>,
);

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 () => {
Expand Down
2 changes: 1 addition & 1 deletion src/Get.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ class ContextlessGet<TData, TError> extends React.Component<
});

if (!this.props.localErrorOnly && this.props.onError) {
this.props.onError(error);
this.props.onError(error, () => this.fetch(requestPath, thisRequestOptions));
}

return null;
Expand Down
46 changes: 43 additions & 3 deletions src/Mutate.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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(<div />);

const onError = jest.fn();

render(
<RestfulProvider base="https://my-awesome-api.fake" onError={onError}>
<Mutate verb="POST" path="">
{children}
</Mutate>
</RestfulProvider>,
);

// 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 () => {
Expand Down
2 changes: 1 addition & 1 deletion src/Mutate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ class ContextlessMutate<TData, TError> extends React.Component<
});

if (!this.props.localErrorOnly && this.props.onError) {
this.props.onError(error);
this.props.onError(error, () => this.mutate(body, mutateRequestOptions));
}

throw error;
Expand Down

0 comments on commit 4b27585

Please sign in to comment.