중복 fetch 해결하기

📌 문제 정의

최근 Inertia.js + React + Laravel 조합으로 프로젝트를 진행하던 중, Context 내부에서 fetch가 두 번 이상 발생하는 문제를 겪었습니다.

  • Context 내부에서 초기 fetch 요청을 보내고 있었고, 이 요청은 특정 state에 의존하고 있었습니다.
  • 그런데 페이지 이동 시 Context가 재마운트되며 initialState가 다시 적용되었고, 결과적으로 fetch가 두 번 이상 실행되었습니다.
  • 이로 인해 드물게 초기값이 UI에 반영되는 문제, 혹은 깜빡임(flickering)이 발생했습니다.

⚙️ 문제 원인 분석

Inertia 환경에선 다음과 같은 제약이 있었습니다:

  1. Context Provider를 <App /> 위에 둘 수 없는 구조
    → Laravel에서 렌더링되는 Blade 파일이 있고, 그 안에서 React가 마운트되기 때문에 Context를 루트 최상단에서 감싸는 게 사실상 불가능했습니다.

  2. Inertia 페이지 이동 시 전체 컴포넌트가 재마운트됨
    → 라우터보다 하위에서 Provider가 선언돼 있으므로 페이지 이동마다 Context가 새롭게 초기화됨.

  3. useReducerinitialState는 첫 마운트 시 한 번만 적용됨
    → 하지만 페이지 전환마다 Context 자체가 재마운트되므로, 결과적으로 initialState가 자주 재적용되고 fetch가 반복됨.

🛠️ 해결 과정

시도 1. initialStatepageProps를 활용

다행히 stateurl에 의존하고 있었기 때문에, 아래처럼 initialState를 동적으로 설정해 문제를 우회할 수 있었습니다:

const pageProps = usePage();
const [state, dispatch] = useReducer(
  adminReducer,
  getInitialAdminState(pageProps), // 초기값을 props로부터 설정
);

하지만 이런 방법은 한계가 있습니다.

  • 초기화 기준이 URL이나 props처럼 외부로부터 주어지는 경우에만 동작합니다.
  • 사용자의 클릭이나 입력처럼 내부 상태 변화에 의존하는 경우엔 쓸 수 없습니다.

시도 2. 초기값을 null로 하고, useEffect에서 조건부 fetch

보다 범용적인 해결책으로 아래 방식을 채택했습니다:

  1. 상태를 최소한으로 정의 (targetId: null)
  2. targetId가 설정되면 그때 fetch를 트리거
  3. 원하는 컴포넌트에서 명시적으로 dispatch를 통해 targetId 설정
const [state, dispatch] = useReducer(reducer, {
  targetId: null,
  data: null,
  loading: false,
});

useEffect(() => {
  if (!state.targetId) return;

  dispatch({ type: 'FETCH_START' });
  fetchData(state.targetId)
    .then((data) => dispatch({ type: 'FETCH_SUCCESS', payload: data }))
    .catch(() => dispatch({ type: 'FETCH_FAIL' }));
}, [state.targetId]);

컴포넌트에서는 다음과 같이 트리거합니다:

onClick={() => dispatch({ type: 'SET_ID', payload: 123 })}

✨ 배운 점 & 인사이트

  • Context는 위치가 전부다.
    최상단에서 감싸지 못하는 구조라면, initialState의 의존성만으로 비즈니스 로직을 구성하긴 어렵다.

  • 초기값은 최소화하고, side-effect로 동작을 분리하자.
    useEffect는 의존성 기반으로 fetch 동작을 분리할 수 있어 훨씬 유연하고 안정적이다.

  • 상태 기반 조건부 fetch는 깜빡임을 줄이고 예측 가능성을 높여준다.
    특히 초기값이 null인 상태에서 fetch를 막고, 필요한 시점에만 명확히 트리거하는 방식은 유지보수성과 안정성 측면에서 매우 강력했다.

  • Context로 모든 걸 해결하려 하지 말자.
    팀 상황상 전역 상태 라이브러리를 쓰지 않기로 했지만, 이런 문제가 반복된다면 Zustand를 쓰는게 편한다!