일상이 개발

React 상태 동기화 이슈 완전 정복: Form ↔ 전역 상태 ↔ URL 쿼리 흐름 정리

디어노미 2025. 4. 9. 17:33
반응형

🔁 React 상태 동기화 이슈 해결법 (Form ↔️ 전역 상태 ↔️ URL 상태)

React 앱을 개발하다 보면 이런 상황들을 자주 마주칩니다.

  • 검색 조건이 Form에 입력되어 있음
  • 이 값이 전역 상태에도 저장되어야 함
  • 또한 URL 쿼리로도 반영되어야 함 (예: ?q=react&page=2)

이 세 가지 상태(Form 입력값, 전역 상태, URL)는 서로 영향을 주기 때문에, 동기화가 엉키면 아래와 같은 문제가 생깁니다.

  • 페이지 이동 후 값이 초기화됨
  • 뒤로가기/앞으로가기 시 상태가 반영되지 않음
  • 사용자 경험이 뒤죽박죽됨

🎯 목표: 세 가지 상태를 일관되게 관리하기

  • Form의 입력값은 사용자가 직접 제어함
  • 전역 상태는 다른 컴포넌트와 공유됨
  • URL은 브라우저 히스토리 + 새로고침 복구 기능 제공

따라서 아래와 같은 흐름을 설계하는 것이 핵심입니다.


Form 입력 변경 → 전역 상태 변경 → URL 쿼리 반영
OR
URL 쿼리 → 전역 상태 초기화 → Form 반영

1️⃣ Form ↔️ 전역 상태 연동

기본적인 구조는 Controlled Input입니다.


const SearchForm = () => {
  const { keyword, setKeyword } = useSearchStore();

  return (
    <input
      type="text"
      value={keyword}
      onChange={(e) => setKeyword(e.target.value)}
    />
  );
};

여기서 useSearchStore는 Zustand, Recoil, Redux 등 원하는 상태관리 도구로 전역 상태를 관리합니다.

✅ 팁: Form 안에서 local state를 써도 되지만, 페이지 전체에서 상태를 공유해야 한다면 전역 상태로 바로 연결하는 것이 깔끔합니다.


2️⃣ 전역 상태 ↔️ URL 쿼리 연동

다음은 전역 상태를 URL 쿼리로 동기화하는 방법입니다.


import { useEffect } from 'react';
import { useRouter } from 'next/router';

const useSyncQuery = () => {
  const router = useRouter();
  const { keyword, page, setKeyword, setPage } = useSearchStore();

  // 상태 → URL 반영
  useEffect(() => {
    router.replace({
      pathname: router.pathname,
      query: { q: keyword, page }
    }, undefined, { shallow: true });
  }, [keyword, page]);

  // URL → 상태 초기화
  useEffect(() => {
    const { q, page } = router.query;
    if (q) setKeyword(q);
    if (page) setPage(Number(page));
  }, [router.isReady]);
};

✅ shallow routing을 사용하면 전체 페이지가 다시 렌더링되지 않으므로 성능에 이점이 있습니다.


3️⃣ Form ↔️ URL 직접 연동하기

Form 입력값을 바로 URL에 반영하고 싶다면 debounce 기법을 추가하면 깔끔합니다.


import { useRouter } from 'next/router';
import { useDebouncedCallback } from 'use-debounce';

const SearchInput = () => {
  const router = useRouter();

  const updateQuery = useDebouncedCallback((value) => {
    router.replace({
      pathname: router.pathname,
      query: { q: value }
    }, undefined, { shallow: true });
  }, 500);

  const onChange = (e) => {
    updateQuery(e.target.value);
  };

  return <input type="text" onChange={onChange} />;
};

✅ use-debounce 라이브러리는 자동 딜레이 처리를 해줘서 깔끔한 UX를 만드는데 유용합니다.


4️⃣ URL 기반 상태 초기화 (초기 진입 시)

초기 진입 시 URL 쿼리를 기반으로 Form과 전역 상태를 초기화해야 합니다.


useEffect(() => {
  const { q, page } = router.query;
  if (q) setKeyword(q);
  if (page) setPage(Number(page));
}, [router.isReady]);

✅ router.isReady를 체크하지 않으면 초기에는 query 값이 비어 있을 수 있으니 주의해야 합니다.


5️⃣ 공통된 흐름 정리

  • Form → 상태 변경 → URL 동기화
  • URL → 상태 초기화 → Form 렌더링 반영

이 흐름을 분리된 hook이나 유틸로 정리해두면 여러 페이지에서 공통적으로 활용할 수 있습니다.


6️⃣ 상태 불일치 버그를 방지하는 팁

  • 초기 상태 값은 query에서 가져오기
  • Form에서 직접 상태를 제어하지 말고, 전역 상태에서 가져오기
  • router.replaceshallow: true를 꼭 설정
  • 페이지 이동 시 reset 동작이 필요하다면 useEffect cleanup으로 clear

✅ 마무리 요약

React 앱에서 Form ↔ 전역 상태 ↔ URL 상태를 동시에 관리하려면, 명확한 흐름 설계와 각 단계의 책임을 구분하는 것이 핵심입니다.

  • Form은 사용자 입력의 시작점
  • 전역 상태는 공유와 상태 복구를 위한 기준점
  • URL은 히스토리와 재접속 복원 기능

이 세 가지를 각각 역할에 맞게 구성하고, 필요한 방향으로만 동기화해주는 전략을 적용하면 유지보수가 편리하고 UX도 향상됩니다.


💬 다음 글에서는?

이후 글에서는 "Next.js + App Router 환경에서 서버 상태 및 URL 기반 라우팅 최적화"와 같이 실제 앱 환경에서 쓸 수 있는 고급 상태 설계 전략을 소개할 예정입니다.

반응형