
Codefug Blog

fetch, scroll to Top, infinite scroll 라이브러리 제거
setState의 비동기 호출
setState는 비동기로 호출됩니다(setState 호출이 비동기이며, setState 자체는 동기 함수입니다. https://velog.io/@jay/setStateisnotasync ). fetch에 state를 전달할 때 이전 state가 들어가는 경우가 발생할 수 있습니다. useEffect를 사용하여 해결할 수 있습니다.
const [id, setId] = useState(false);
let idolId = false;
const handleClick = (e) => {
if (!idolId) return;
setCredit(credit - 1000);
wrappedFunction(idolId);
};
useEffect(() => {
if (id !== false) {
idolId = id;
}
}, [id]);
wrappedFunction에 fetch가 들어가 있습니다. fetch에 state인 id를 그대로 전달하게 되면 setId가 바뀐지 확인할 수 없는 상태에서 id가 전달되어 로직이 꼬일 수 있습니다.
id가 바뀌면 변수를 업데이트하고, handler에서 업데이트된 변수를 사용하는 방식으로 비동기 처리가 완료된 state를 얻을 수 있습니다.
리액트는 가상돔의 변경사항을 한번에 보내는 batching을 사용합니다. batching이 완료되기 전에 fetch를 보내면 에러가 발생할 수 있습니다.
useEffect로 id에 따라 idolId를 갱신하는 방식으로 처리하면 업데이트된 Id로 fetch 요청을 보낼 수 있습니다.
fetch에서 POST를 보낼 때 header에 존재하는 Content-Type은 실제 보내는 데이터의 Type과 일치해야 한다.
export async function postVotes(Id) {
const URL = `${BASE_URL}/votes`;
const response = await fetch(URL, {
method: "POST",
mode: "cors",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ idolId: +Id }),
});
return response.json();
}
네트워크 처리를 할 때 state를 정의하는 함수에 setter 함수를 같은 스코프에 넣으면 무한 루프가 발생할 수 있습니다. setter가 state를 변경하고 다시 렌더링되는 과정이 반복됩니다.
const [status, setStatus] = useState({
isLoading: true,
errorMessage: null,
});
const wrappedFunction = async (...args) => {
try {
setStatus((prevStatus) => ({
...prevStatus,
isLoading: true,
errorMessage: null,
}));
return await funcAsync(...args);
} catch (error) {
setStatus((prevStatus) => ({
...prevStatus,
errorMessage: error,
}));
} finally {
setStatus((prevStatus) => ({
...prevStatus,
isLoading: false,
}));
}
};
state가 변경되는 곳에 fetch 처리를 하면 계속 fetch를 하게 되어 오류가 발생합니다. useEffect를 이용해서 deps에 따라서만 fetch를 호출하도록 처리합니다.
useEffect(() => {
const fetchData = async () => {
const data = await wrappedFunction({ gender });
console.log(data);
};
fetchData();
}, [gender]);
useEffect는 렌더링마다 cleanup > setup > cleanup 로직을 실행합니다. development 환경에서는 2번 더 실행됩니다.
inView deps를 가진 useEffect에 fetch를 넣고 다른 곳에서도 넣었더니 기본 4번 fetch에 첫 화면의 intersection observer api도 작동하여 fetch를 8번 보냈습니다.
useEffect는 deps가 있어도 첫 렌더링 때 실행됩니다. 같은 로직에 다른 deps를 사용하는 경우 하나의 useEffect로 합쳐서 사용해야 합니다.
페이지 이동 시 router를 사용하게 되는데, router의 기본 동작이 브라우저의 기본 동작을 막기 때문에 스크롤 위치를 처리하기 어렵습니다.
React는 SPA 특성을 가지고 있어 페이지가 직접 바뀌지 않고 JS로 페이지의 일부만 변경됩니다. 스크롤 위치가 그대로 유지되는 것은 SPA의 특성입니다.
// ScrollToTop.js
import { useEffect } from "react";
import { useLocation } from "react-router-dom";
export default function ScrollToTop(props) {
const { pathname } = useLocation();
useEffect(() => {
window.scrollTo(0, 0);
}, [pathname]);
return <>{props.children}</>;
}
위 코드로 해결할 수 있습니다.
useLocation 훅으로 현재 위치를 가져온 후, useEffect로 현재 위치가 변경되면 window.scrollTo를 사용하여 스크롤을 상단으로 이동시킵니다.
라이브러리 없이 순수 intersection observer API를 사용해서 infinite scroll을 구현하기로 했습니다.
기존에 사용하던 useInView를 intersection observer API로 구현했습니다.
import { useEffect, useRef, useState } from "react";
export default function useCustomInView(option) {
const [inView, setInView] = useState(false);
const ref = useRef(null);
useEffect(() => {
const observer = new IntersectionObserver(
([{ isIntersecting }]) => {
setInView(isIntersecting);
},
{ ...option },
);
if (ref.current && ref.current instanceof Element) {
observer.observe(ref.current);
}
return () => {
if (observer) observer.disconnect();
};
}, [option]);
return { ref, inView };
}
useInview와 똑같이 ref와 inView를 반환하기 때문에 기존 코드에서 useInView를 useCustomInView로 바꾸는 것만으로 마이그레이션할 수 있습니다.
기타 구현 지식svg component를 사용해서 svg 타입 이미지를 쉽게 커스터마이징 할 수 있다. svgr 사이트를 통해서 svg를 컴포넌트로 변환하고 export로 받아오면 된다.
코드잇 프로젝트 진행 중에 겪은 경험들입니다.
문제가 되는 부분이 있다고 요청 주시면 삭제하겠습니다.
감사합니다.