fix(ui): use atomic update in useFetchGet

This commit is contained in:
Łukasz Mierzwa
2020-07-25 22:14:18 +01:00
committed by Łukasz Mierzwa
parent a38c1fc4a1
commit a24b71166b
2 changed files with 72 additions and 41 deletions
+22 -21
View File
@@ -1,6 +1,6 @@
import React from "react";
import { renderHook } from "@testing-library/react-hooks";
import { renderHook, act } from "@testing-library/react-hooks";
import { mount } from "enzyme";
@@ -31,7 +31,7 @@ describe("useFetchGet", () => {
it("sends a GET request", async () => {
const { waitForNextUpdate } = renderHook(() =>
useFetchGet("http://localhost/ok")
useFetchGet<string>("http://localhost/ok")
);
await waitForNextUpdate();
@@ -45,7 +45,7 @@ describe("useFetchGet", () => {
it("sends correct headers", async () => {
const { waitForNextUpdate } = renderHook(() =>
useFetchGet("http://localhost/ok")
useFetchGet<string>("http://localhost/ok")
);
await waitForNextUpdate();
@@ -61,12 +61,14 @@ describe("useFetchGet", () => {
it("doesn't send any request if autorun=false", async () => {
const { result, waitForNextUpdate } = renderHook(() =>
useFetchGet("http://localhost/ok", { autorun: false })
useFetchGet<string>("http://localhost/ok", { autorun: false })
);
expect(fetchMock.calls()).toHaveLength(0);
result.current.get();
act(() => {
result.current.get();
});
await waitForNextUpdate();
expect(fetchMock.calls()).toHaveLength(1);
@@ -75,7 +77,7 @@ describe("useFetchGet", () => {
it("will retry failed requests", async () => {
const { result, waitForNextUpdate } = renderHook(() =>
useFetchGet("http://localhost/error")
useFetchGet<string>("http://localhost/error")
);
// initial state
@@ -129,7 +131,7 @@ describe("useFetchGet", () => {
it("response is updated after successful fetch", async () => {
const { result, waitForNextUpdate } = renderHook(() =>
useFetchGet("http://localhost/ok")
useFetchGet<string>("http://localhost/ok")
);
expect(result.current.response).toBe(null);
@@ -153,7 +155,7 @@ describe("useFetchGet", () => {
});
const { result, waitForNextUpdate } = renderHook(() =>
useFetchGet("http://localhost/500/json")
useFetchGet<string>("http://localhost/500/json")
);
await waitForNextUpdate();
@@ -171,7 +173,7 @@ describe("useFetchGet", () => {
});
const { result, waitForNextUpdate } = renderHook(() =>
useFetchGet("http://localhost/500/text")
useFetchGet<string>("http://localhost/500/text")
);
await waitForNextUpdate();
@@ -184,7 +186,7 @@ describe("useFetchGet", () => {
it("error is updated after failed fetch", async () => {
const { result, waitForNextUpdate } = renderHook(() =>
useFetchGet("http://localhost/error")
useFetchGet<string>("http://localhost/error")
);
expect(result.current.response).toBe(null);
@@ -210,7 +212,7 @@ describe("useFetchGet", () => {
});
const { result, waitForNextUpdate } = renderHook(() =>
useFetchGet("http://localhost/json/invalid")
useFetchGet<string>("http://localhost/json/invalid")
);
expect(result.current.response).toBe(null);
@@ -236,7 +238,7 @@ describe("useFetchGet", () => {
});
const Component = () => {
const { response, error, isLoading } = useFetchGet(
const { response, error, isLoading } = useFetchGet<string>(
"http://localhost/slow/ok"
);
return (
@@ -265,7 +267,7 @@ describe("useFetchGet", () => {
});
const Component = () => {
const { response, error, isLoading } = useFetchGet(
const { response, error, isLoading } = useFetchGet<string>(
"http://localhost/slow/500"
);
return (
@@ -293,7 +295,7 @@ describe("useFetchGet", () => {
});
const Component = () => {
const { response, error, isLoading } = useFetchGet(
const { response, error, isLoading } = useFetchGet<string>(
"http://localhost/slow/error"
);
return (
@@ -322,7 +324,7 @@ describe("useFetchGet", () => {
});
const Component = () => {
const { response, error, isLoading } = useFetchGet(
const { response, error, isLoading } = useFetchGet<string>(
"http://localhost/slow/json/invalid"
);
return (
@@ -348,7 +350,7 @@ describe("useFetchGet", () => {
});
const Component = () => {
const { response, error, isLoading } = useFetchGet(
const { response, error, isLoading } = useFetchGet<string>(
"http://localhost/slow/text"
);
return (
@@ -385,11 +387,10 @@ describe("useFetchGet", () => {
jest.useRealTimers();
const Component = () => {
const {
response,
error,
isLoading,
} = useFetchGet("http://localhost/slow/body", { fetcher: fetcher });
const { response, error, isLoading } = useFetchGet<string>(
"http://localhost/slow/body",
{ fetcher: fetcher }
);
return (
<span>
<span>{response}</span>
+50 -20
View File
@@ -14,6 +14,14 @@ export interface FetchGetOptionsT {
fetcher?: null | FetchFunctionT;
}
interface ResponseState<T> {
response: null | T;
error: null | string;
isLoading: boolean;
isRetrying: boolean;
retryCount: number;
}
const useFetchGet = <T>(
uri: string,
{ autorun = true, deps = [], fetcher = null }: FetchGetOptionsT = {}
@@ -26,11 +34,13 @@ const useFetchGet = <T>(
get: () => void;
cancelGet: () => void;
} => {
const [response, setResponse] = useState<T | null>(null);
const [error, setError] = useState<string | null>(null);
const [isLoading, setIsLoading] = useState<boolean>(true);
const [isRetrying, setIsRetrying] = useState<boolean>(false);
const [retryCount, setRetryCount] = useState<number>(0);
const [response, setResponse] = useState<ResponseState<T>>({
response: null,
error: null,
isLoading: autorun,
isRetrying: false,
retryCount: 0,
});
const isCanceled = useRef<boolean>(false);
const cancelGet = useCallback(() => {
@@ -41,11 +51,15 @@ const useFetchGet = <T>(
isCanceled.current = false;
try {
setIsLoading(true);
setRetryCount(0);
setError(null);
setResponse((r) => ({
...r,
isLoading: true,
isRetrying: false,
retryCount: 0,
}));
const res = await promiseRetry(
(retry: (err: Error) => Promise<Response>, number: number) =>
(retry: (err: Error) => Promise<Response>, n: number) =>
(fetcher || fetch)(
uri,
merge(
@@ -55,13 +69,16 @@ const useFetchGet = <T>(
},
CommonOptions,
{
mode: number <= FetchRetryConfig.retries ? "cors" : "no-cors",
mode: n <= FetchRetryConfig.retries ? "cors" : "no-cors",
}
) as RequestInit
).catch((err: Error) => {
if (!isCanceled.current) {
setIsRetrying(true);
setRetryCount(number);
setResponse((r) => ({
...r,
isRetrying: true,
retryCount: n,
}));
return retry(err);
}
}),
@@ -79,18 +96,31 @@ const useFetchGet = <T>(
if (!isCanceled.current) {
if (res.ok) {
setResponse(body);
setResponse({
response: body,
error: null,
isLoading: false,
isRetrying: false,
retryCount: 0,
});
} else {
setError(body);
setResponse({
response: null,
error: body,
isLoading: false,
isRetrying: false,
retryCount: 0,
});
}
setIsLoading(false);
setIsRetrying(false);
}
}
} catch (error) {
setError(error.message);
setIsLoading(false);
setIsRetrying(false);
setResponse((r) => ({
...r,
error: error.message,
isLoading: false,
isRetrying: false,
}));
}
}, [uri, fetcher]);
@@ -100,7 +130,7 @@ const useFetchGet = <T>(
return () => cancelGet();
}, [uri, get, cancelGet, autorun, ...deps]); // eslint-disable-line react-hooks/exhaustive-deps
return { response, error, isLoading, isRetrying, retryCount, get, cancelGet };
return { get, cancelGet, ...response };
};
export { useFetchGet };