데이터를 가져올 때 로딩을 띄우고 싶다면 React Hook의 useState를 활용해서 사용할 수 있다.
로딩 상태값을 만들어서 데이터를 가져오기전까지 로딩을 띄우고, 다 가져왔으면 로딩을 없애주는 로직을 useState 훅을 사용하여 많이 사용해 왔을 것이다.
const [loading, setLoading] = useState(false);
const [data, setData] = useState([]);
useEffect(() => {
setLoading(true);
setError(undefined);
fetch(`...`)
.then((res) => res.json())
.then((data) => {
setData(data);
})
.catch((error) => {
console.error(error);
})
.finally(() => setLoading(false));
}, [...]);
하지만 가져와야 하는 데이터가 많다면 똑같은 로직을 여러개 만들어야 한다. 데이터를 인자로 전달하여 isLoading
과 data
를 반환하는 커스텀 훅을 만들어 보았다.
// ./src/hooks/useLoading.ts
const useLoading = () => {
const [isLoading, setIsLoading] = useState(false);
}
커스텀 훅이니 네이밍을 할 때 앞에 use
를 붙여서 hooks 폴더 안에 만들어준다.
그리고 최상단에 로딩을 띄울 상태값 isLoading
을 false로 초기값을 설정하여 만들어준다.
이 상태값은 데이터를 받아왔는지 여부에 따라서 true, false가 변경된다(값이 변경되는 부분은 아래에서 다룰 예정이다). isLoading이 true라면 로딩 아이콘을 랜더링 하며, false라면 받아온 데이터를 뿌려준다.
// ./src/hooks/useLoading.ts
const useLoading = (action: (...args: ArgumentType) => Promise<T>) => {
const [isLoading, setIsLoading] = useState(false);
}
action
은 인자로 받아오는 콜백함수로 데이터를 처리하는 함수를 받는다. 이 콜백함수는 데이터를 가져와서 상태 값에 저장한 함수를 말한다. 예를 들면 이런 함수다.
const handleGetTodoList = async() => {
const data = await getTodoList();
setData(data || [])
}
이렇게 데이터를 가져오고 처리하는 함수를 useLoading 커스텀 훅에 전달해준다.
콜백함수 action의 반환 값은 사용하는 곳마다 다른 타입을 받아올 수 있도록 하기 위해서 Generic을 사용해준다.
const [...] = useLoading<void>(...)
이렇게 useLoading을 사용하는 곳에서 void
타입을 전달해주면 useLoading의 제너릭을 사용한 곳에 void
가 들어간다고 볼 수 있다:
const useLoading = (action: (...args: ArgumentType) => Promise<void>) => {
const [isLoading, setIsLoading] = useState(false);
}
데이터를 호출 하기 전후로 로딩을 처리할 handleData
함수를 만들어준다.
const handleData = async (...args: ArgumentType) => {
setIsLoading(true);
return await action(...args).finally(() => setIsLoading(false));
};
handleData 함수안에서 인자로 받아온 콜백함수 action을 호출하기 때문에 (useLoading에서 받아온 인자 action의 모든 인자) args를 똑같이 전개연산자로 넣어준다. 그런 다음 콜백함수 action을 호출하는 부분에서 args를 전달해준다.
useLoading 훅의 하이라이트이다. 로딩을 띄우고 데이터를 받아오는 일을 이 함수가 수행한다. 아래 단계를 거친다.
isLoading
을 false로 바꾸어준다.마지막으로 여러 곳에서 사용할 수 있도록 isLoading과 handleData를 반환해준다. handleData를 반환해주는 이유는 사용자의 인터렉션으로 form이 제출 됐다던가, 어떤 버튼을 클릭했을 때 handleData로 데이터를 요청하고 받기위함이다.
...
return [isLoading, handleData];
import { useState } from 'react';
type UseLoadingReturnType<T> = [boolean, (...args: any[]) => Promise<T>];
const useLoading = <T>(
action: (...args: any[]) => Promise<T>
): UseLoadingReturnType<T> => {
const [isLoading, setIsLoading] = useState(false);
const handleData = async (...args: any[]) => {
setIsLoading(true);
return await action(...args).finally(() => setIsLoading(false));
};
return [isLoading, handleData];
};
export default useLoading;
useLoading
을 다시 한번 설명하자면, 데이터를 받아오기 전까지 로딩을 띄우고 데이터를 성공적으로 받아오면 데이터를 뿌리는 커스텀 훅이다.
그래서 우리가 useLoading에서 사용할 수 있는 값은 데이터를 받아오는지 모니터링 하면서 로딩을 결정하는 Boolean 값 isLoading, 그리고 사용자 인터렉션이 있을 때 데이터를 가져오는 handleData 함수, 이 두개다.
Todo 리스트를 추가한다고 치자.
// Todo.tsx
const [isLoading, createTodoLists] = useLoading(handleCreateTodos);
const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
createTodos(inputText, setTodos);
}
return (
<>
{isLoading ? <Spinner /> : <TodoData />}
</>
);
이런 식으로 사용할 수 있겠다.
만약 로딩 로직을 커스텀 훅으로 빼지 않았다면 handleSubmit
함수안에 setLoading
과 handleCreate()
등 로딩과 데이터를 처리하는 코드들이 뭉쳐있어 가독성이 떨어질 수 있다.
useLoading
은 로딩만 처리하는 커스텀 훅으로 단순하게 만들수도 있겠지만 로딩만 처리하기엔 useState로 쓰는 것과 코드 상의 차이가 없다고 느껴졌다. 그래서 주로 데이터를 받아올 때 만들어지는 것을 보고 데이터 로딩 훅을 만들어봤다.
실제로 CRUD 같은 하나 이상의 요청을 각각 받아와야 할 때, 로딩을 4곳에서 4번을 만드는 게 아닌 useLoading 훅을 사용하면 코드가 훨씬 간결해진다.
이상으로, useLoading 커스텀 훅에 대한 소개 및 사용법을 정리해보았다.