url 과 초기값을 인자로 받아, 데이터를 패치한 뒤의 결과값과 loading Status 를 반환하는 Hooks 가 있다.
useStaleRefresh.js
import { useState, useEffect } from "react";
const CACHE = {};
export default function useStaleRefresh(url, defaultValue = []) {
const [data, setData] = useState(defaultValue);
const [isLoading, setLoading] = useState(true);
useEffect(() => {
// cacheID is how a cache is identified against a unique request
const cacheID = url;
// look in cache and set response if present
if (CACHE[cacheID] !== undefined) {
setData(CACHE[cacheID]);
setLoading(false);
} else {
// else make sure loading set to true
setLoading(true);
setData(defaultValue);
}
// fetch new data
fetch(url)
.then((res) => res.json())
.then((newData) => {
CACHE[cacheID] = newData;
setData(newData);
setLoading(false);
});
}, [url, defaultValue]);
return [data, isLoading];
}
맨 처음 data 에는 인자로 받은 defaultValue 값이, isLoading 에는 true 가 반환된다.
첫 랜더링이 완료 된 후 useEffect 가 실행되어, 캐시된 값이 있는지 확인한다. 처음엔 당연히 캐시된 값이 없으므로 data에는 defaultValue 값이, isLoading 에는 true 가 반환된다.
API fetch가 완료 된 이후 해당 값을 캐싱한 뒤, data 에는 패치로 받은 response 를, isLoading 에는 false 가 반환된다.
useStaleRefresh 에 인자로 넘겨주는 url 값이 변경될 경우, useEffect 가 실행되어 data 는 defaultValue, isLoading 은 true 를 반환한다.
fetch 가 끝난 후 response 를 캐싱한다. data 는 response 값, isLoading 은 false 가 반환된다.
맨 처음 요청을 보냈던 url 을 useStaleRefresh 의 인자로 다시 넣어주면 캐싱해 두었던 값이 있으므로, data 는 캐싱해두었던 값, isLoading 은 false 를 반환한다.
fetch 가 끝난 후, 갱신된 response 값을 캐싱한 후, data 는 새로운 response 값, isLoading 은 false 를 반환한다.
훅스를 테스트 하기 위해서는 testing-library/react 의 renderHook 을(를) 이용하면 편하다.
참고) renderHook 코드
WaitFor 코드(꼭 보는걸 추천)
renderHook 요지.. -> 처음 useEffect 가 실행됨.
useStaleRefresh.test.ts
// 훅스 내부 fetch 를 모킹하기 위한 함수
function fetchMock(url, suffix = "") {
return new Promise((resolve) =>
setTimeout(() => {
resolve({
json: () =>
Promise.resolve({
data: url + suffix,
}),
});
}, 200 + Math.random() * 300)
);
}
describe('useStaleRefresh test', () => {
beforeAll(() => {
jest.spyOn(global, "fetch").mockImplementation(fetchMock);
});
test('useStaleRefresh 는 올바르게 작동한다.', () => {
const defaultValue = 'defaultValue';
const { result, waitFor, rerender } = renderHook(
() => useStaleRefresh('url1', defaultValue)
);
expect(result.current[0]).toEqual(defaultValue);
expect(result.current[1]).toBe(true);
await waitFor(() => {
expect(result.current[0].data).toEqual('url1');
});
expect(result.current[1].data).toBe(false);
rerender({ url : 'url2'});
expect(result.current[0]).toEqual(defaultValue);
expect(result.current[1]).toBe(true);
await waitFor(() => {
expect(result.current[0].data).toEqual('url2');
});
expect(result.current[1].data).toBe(false);
});
https://www.toptal.com/react/testing-react-hooks-tutorial
https://testing-library.com/