개발/react

[React] Custom Hooks 하나로 Data-fetching 관리 하기

waterpole-dev 2022. 9. 26. 13:02
반응형

안녕하세요.

오늘은 한 개의 react custom hook으로 GET/POST/PUT/DELETE 등 여러 요청을 한 번에 관리해 보겠습니다.


hooks

 

useFetch.js

import { useState, useCallback } from 'react';

// 필요한 파라미터를 밖으로 리턴해준 메모이제이션 처리 된
// 중첩함수 sendRequest에서 받기 때문에
// 이 hook을 사용하는 component에서 useFetch함수에 useCallback과 같은
// 메모이제이션을 해줄 필요가 없음

const useFetch = () => {
    const [isLoading, setIsLoading] = useState(false);
    const [error, setError] = useState(null);

    const sendRequest = useCallback(async (requestConfig, applyData) => {
        const { url, method, headers, body } = requestConfig;

        setIsLoading(true);
        setError(null);

        try {
            const response = await fetch(url, {
                method: method ? method : 'GET',
                headers: headers ? headers : {},
                body: body ? JSON.stringify(body) : null,
            });

            if (!response.ok) {
                throw new Error('Request failed!');
            }

            const data = await response.json();
            applyData(data);
        } catch (err) {
            setError(err.message);
        }
        setIsLoading(false);
    }, []);

    return { isLoading, error, sendRequest };
};

export default useFetch;

보통 fetch를 하게되면 로딩, 실패 상태도 같이 관리해줘야 하기 때문에 useFetch hook에서 한 번에 관리합니다.


사용 예

 

app.js (GET)

import { useEffect, useState } from 'react';

import Lists from './components/Lists';
import NewList from './components/NewList';
import useFetch from './hooks/useFetch';

function App() {
    const [lists, setLists] = useState([]);
    const { isLoading, error, sendRequest: fetchLists } = useFetch();

    useEffect(() => {
        // 이 함수를 useEffect 밖으로 빼도 문제는 없지만, 무한루프에 빠지지 않으려면
        // hooks의 상태변경으로 인해 컴포넌트가 재랜더링 될 때
        // 함수도 재생성 되기 때문에 useCallback을 써줘야 함
        const transformLists = (listObj) => {
            const loadedLists = [];

            for (const listKey in listObj) {
                loadedLists.push({ id: listKey, text: listObj[listKey].text });
            }

            setLists(loadedLists);
        };

        fetchLists(
            {url: 'https://@@@@@.firebaseio.com/lists.json'},
            transformLists
        );
    }, []);

    const listAddHandler = (list) => {
        setLists((prevLists) => prevLists.concat(list));
    };

    return (
        <>
            <NewList onAddList={listAddHandler} />
            <Lists items={lists} loading={isLoading} error={error} onFetch={fetchLists} />
        </>
    );
}

export default App;

app.js는 get 예시 입니다.

Lists는 그냥 데이터 받아서 뿌려주기만 하는 components라서 예시 코드 없습니다.

 

 

NewList.js (POST)

import ListAddForm from './ListAddForm';
import useFetch from '../../hooks/useFetch';

const NewList = (props) => {
    const { isLoading, error, sendRequest: sendListRequest } = useFetch();

    // 방법 1 (bind())
    const createList = (listText, listData) => {
        const generatedId = listData.name;
        const createdList = { id: generatedId, text: listText };
        props.onAddList(createdList);
    };

    // enterListHandler이 함수는 버튼 클릭 시에만 실행돼서
    // sendListRequest()의 상태변경에의한 무한루프에 빠지지 않기 때문에
    // useCallback을 사용하지 않아도 괜찮음
    const enterListHandler = async (listText) => {
        // 방법 2
        // const createList = (listData) => {
        // 	const generatedId = listData.name;
        // 	const createdList = { id: generatedId, text: listText };
        // 	props.onAddList(createdList);
        // };

        sendListRequest(
            {
                url: 'https://@@@@@.firebaseio.com/lists.json',
                method: 'POST',
                body: { text: listText },
                headers: {
                    'Content-Type': 'application/json',
                },
            },
            createList.bind(null, listText)
        );
    };

    return (
        <section>
            <ListAddForm onEnterList={enterListHandler} loading={isLoading} />
            {error && <p>{error}</p>}
        </section>
    );
};

export default NewLIst;

피드백, 질문, 댓글 언제나 환영입니다!

감사합니다 :)

반응형