React Router v7 - (3) API Hooks

장유진·2026년 3월 2일

React Router v7

목록 보기
3/5
post-thumbnail

1. useActionData

가장 최근에 실행된 action의 결과를 반환한다. action이 실행된 적 없다면 undefined를 반환한다.

import { Form, useActionData } from "react-router";

export async function action({ request }) {
  const body = await request.formData();
  const name = body.get("visitorsName");
  return { message: `Hello, ${name}` };
}

export default function Invoices() {
  const data = useActionData();
  return (
    <Form method="post">
      <input type="text" name="visitorsName" />
      {data ? data.message : "Waiting..."}
    </Form>
  );
}

❓ clientAction의 결과는 반환하지 않는가??

공식문서에는 clientAction은 적혀있지 않지만 useLoaderData와 비슷하게 action 또는 clientAction의 결과값을 반환할 것으로 예상된다. 확실하지 않음!

2. useAsyncError

가장 가까운 상위 <Await> 컴포넌트에서 reject된 Promise의 에러 값을 반환한다. 즉, reject()에 인자로 전달된 값 또는 throw 키워드 뒤에 사용된 값을 반환한다.

import { Await, useAsyncError } from "react-router";

function ErrorElement() {
  const error = useAsyncError();
  return (
    <p>Uh Oh, something went wrong! {error.message}</p>
  );
}

// somewhere in your app
<Await
  resolve={promiseThatRejects}
  errorElement={<ErrorElement />}
/>;

3. useAsyncValue

가장 가까운 상위 <Await> 컴포넌트에서 resolve된 Promise의 결과 값을 반환한다.

function SomeDescendant() {
  const value = useAsyncValue();
  // ...
}

// somewhere in your app
<Await resolve={somePromise}>
  <SomeDescendant />
</Await>;

❗ 보통 <Await> 컴포넌트는 render props를 사용해서 자식을 렌더링 할 수 있지만, 컴포넌트 구조가 복잡해질 수 있다. useAsyncError 또는 useAsyncValue를 사용하면 로직을 깔끔하게 분리할 수 있고 prop drilling도 피할 수 있다.

4. useBeforeUnload

window의 beforeunload 이벤트 발생 시 실행될 콜백함수를 설정한다. 사용자가 페이지를 떠나기 전 실행할 동작을 등록할 수 있다.

import { useBeforeUnload } from "react-router";

function MyComponent() {
  const [isDirty, setIsDirty] = React.useState(false);

  useBeforeUnload(
    React.useCallback(() => {
      if (isDirty) {
        localStorage.setItem("draft", "아직 저장되지 않은 내용이 있어요!");
      }
    }, [isDirty])
  );

  return (
    <input 
      type="text" 
      onChange={() => setIsDirty(true)} 
      placeholder="내용을 입력하세요..." 
    />
  );
}

params

  • callback: beforeunload 이벤트 발생 시 실행할 콜백함수
  • options.capture?: true일 경우 이벤트 캡쳐링 단계에서 실행된다. (default: false)

5. useBlocker

SPA 내부에서의 navigation(페이지 이동)을 차단하고 Blocker 객체를 반환한다.. 페이지 이동 전 사용자에게 confirm 창을 보여주거나 양식을 덜 작성한 상태에서 실수로 페이지를 나가는 것을 방지할 때 사용한다.

// Boolean version
let blocker = useBlocker(value !== "");

// Function version
let blocker = useBlocker(
  ({ currentLocation, nextLocation, historyAction }) =>
    value !== "" &&
    currentLocation.pathname !== nextLocation.pathname
);

params

  • shouldBlock: navigation 차단 여부. boolean 또는 BlockerFunction 타입이다.

BlockerFunction 타입

interface Path {
    pathname: string;
    search: string;
    hash: string;
}

interface Location<State = any> extends Path {
    state: State;
    key: string;
}

declare enum Action {
    Pop = "POP",
    Push = "PUSH",
    Replace = "REPLACE"
}

type BlockerFunction = (args: {
  currentLocation: Location;  
  nextLocation: Location;       
  historyAction: HistoryAction; 
}) => boolean;

reutrns

  • Blocker 객체
    • state
      • unblocked: 블로커가 대기 상태이며, 아무 navigation도 방해하지 않은 상태
      • blocked: 블로커가 navigation을 차단한 상태
      • proceeding: 차단되었던 navigation이 proceed() 함수 호출에 의해 다시 실행되고 있는 상태
    • location
      • blocked 상태일 때: 차단된 navigation의 목적지 Location
      • proceeding 상태일 때: proceed() 호출 이후 실제로 이동 중인 Location
      • unblocked 상태일 때: undefined
    • proceed(): blocked 상태에서 호출하면 차단되었던 navigation을 재개
    • reset(): blocked 상태에서 호출하면 다시 unblocked 상태로 되돌리고 현재 페이지에 그대로 있음

useBeforeUnloaduseBlocker의 차이는?

useBeforeUnload는 사이트 자체를 나갈 때 동작하고 useBlocker는 React Router 앱 안에서 이동할 때 동작한다.

6. useFetcher

navigation을 발생시키지 않고 loaderaction을 실행한다.

import { useFetcher } from "react-router"

function SomeComponent() {
  let fetcher = useFetcher()

  // states are available on the fetcher
  fetcher.state // "idle" | "loading" | "submitting"
  fetcher.data // the data returned from the action or loader

  // render a form
  <fetcher.Form method="post" />

  // load data
  fetcher.load("/some/route")

  // submit data
  fetcher.submit(someFormRef, { method: "post" })
  fetcher.submit(someData, {
    method: "post",
    encType: "application/json"
  })

  // reset fetcher
  fetcher.reset()
}

params

  • options.key?: 각 fetcher에 부여하는 고유 식별자. 기본적으로는 useFetcher를 실행할 때마다 고유한 내부 인스턴스가 생성되지만, 다른 위치에서 호출하는 fetcher가 동일한 상태를 공유하게 하고 싶을 때 동일한 key를 부여하면 된다.

returns

  • FetcherWithComponents 객체 (FetcherWithComponents 타입 상세)
    • state
      • idle: 아무 작업도 하지 않는 대기 상태
      • submitting: 폼 데이터를 전송 중인 상태
      • loading: 데이터를 가져오거나 전송 후 재검증 중인 상태
    • data: action 또는 loader에서 반환한 응답 데이터
    • formData: 현재 서버로 전송되고 있는 FormData 객체
    • formMethod: 현재 전송 중인 요청의 HTTP 메서드
    • formAction: 현재 전송 중인 요청의 대상 URL
    • Form: useFetcher 전용 폼 컴포넌트
    • load: loader 실행
    • submit: action 실행
    • reset: fetcher의 데이터와 상태를 idleundefined로 초기화

❓ data가 loader 응답값인지, action의 응답값인지 어떻게 알 수 있을까?

정확하게 구분하는 속성은 없지만 유추할 수 있는 방법이 몇 가지 있다.

  • 호출 함수로 구분: fetcher.submit 호출 → action, fetcher.load 호출 → loader
  • formMethod 속성으로 구분: formMethodPOST, PUT, PATCH, DELETE인 경우 → action, formMethod가 없거나 GET인 경우 → loader
  • 데이터 구조로 구분

useFetcher를 사용하여 낙관적 업데이트를 하는 방법

  1. 사용자 액션 발생
  2. state가 submitting으로 변경되고 fetcher.formData에 전송 데이터가 채워짐
  3. 컴포넌트에서 fetcher.formData를 확인하여 데이터가 있으면 서버 응답을 기다리지 않고 바로 화면에 반영
  4. 서버 작업이 완료되면 실제 서버 데이터로 렌더링된 UI가 화면을 덮어쓰며 최종 확정
import { useFetcher } from "react-router";

function LikeButton({ post }) {
  const fetcher = useFetcher();

  // 1. 낙관적 상태 결정
  // fetcher.formData가 있다는 것은 현재 전송 중임을 의미함
  const isLiking = fetcher.formData?.get("intent") === "like";
 
  // 서버 데이터보다 fetcher에 있는 '낙관적 데이터'를 우선시함
  const displayLikeCount = isLiking 
    ? post.likes + 1 
    : post.likes;

  return (
    <fetcher.Form method="post" action={`/posts/${post.id}/like`}>
      <input type="hidden" name="intent" value="like" />
      <button type="submit" disabled={isLiking}>
        {displayLikeCount} ❤️ {isLiking ? "(반영 중...)" : ""}
      </button>
    </fetcher.Form>
  );
}

7. useFetchers

현재 활성화된 모든 fetcher 객체들의 배열을 반환한다.

import { useFetchers } from "react-router";

function SomeComponent() {
  const fetchers = useFetchers();
  fetchers[0].formData; // FormData
  fetchers[0].state; // etc.
  // ...
}

returns

8. useFormAction

Form 객체에 전달할 action URL 경로를 계산하여 반환한다.

import { useFormAction } from "react-router";

function SomeComponent() {
  // closest route URL
  let action = useFormAction();

  // closest route URL + "destroy"
  let destroyAction = useFormAction("destroy");
}

params

  • action?: 현재 라우트의 URL에 붙일 action의 경로이다. action을 입력하지 않으면 useFormAction은 현재 라우트의 주소를 반환한다.
  • options.relative?: route일 때 라우트 계층 기준으로 계산, path일 때 URL 경로 기준으로 계산한다. (default: route)

9. useHref

현재 위치를 기준으로 상대 경로를 절대 경로로 변환하여 문자열로 반환한다.

import { useHref } from "react-router";

function SomeComponent() {
  let href = useHref("some/where");
  // "/resolved/some/where"
}

params

  • to: 계산할 상대 경로
  • options.relative?: route일 때 라우트 계층 기준으로 계산, path일 때 URL 경로 기준으로 계산한다. (default: route)

10. useInRouterContext

컴포넌트가 Router 내부에 있는지 여부를 boolean으로 반환한다.

import { useInRouterContext, useNavigate } from "react-router";

function MySharedButton() {
  const inRouter = useInRouterContext();
  const navigate = useNavigate(); // 이대로 라우터 밖에서 쓰면 에러 발생!

  const handleClick = () => {
    if (inRouter) {
      // 라우터 안이라면 페이지 이동
      navigate("/home");
    } else {
      // 라우터 밖(예: 단순 랜딩 페이지나 테스트 환경)이라면 일반 창 이동
      window.location.href = "/home";
    }
  };

  return <button onClick={handleClick}>이동</button>;
}

11. useLinkClickHandler

<Link> 컴포넌트의 클릭 이벤트 동작을 정의하여 반환한다. 커스텀한 <Link> 컴포넌트를 구현해야 할 때 사용한다.

import { useLinkClickHandler } from "react-router";

function CustomAnchor({ to, children }) {
  // 1. 클릭 핸들러를 생성합니다.
  const handleClick = useLinkClickHandler(to, {
    replace: false, // 히스토리를 쌓을지 여부
    state: { from: "custom_button" } // 상태 전달 가능
  });

  return (
    // 2. 일반 a 태그의 onClick에 연결합니다.
    <a href={to} onClick={handleClick}>
      {children}
    </a>
  );
}

params

  • to: 이동할 목적지 주소
  • options
    • target?: _blank, _self 등 링크의 target 지정 (default: undefined)
    • replace?: true로 설정하면 브라우저 히스토리 스택을 쌓지 않고 덮어쓰기함 (default: false)
    • unstable_mask?: 실제 주소 대신 주소창에 표시될 URL 지정 (default: undefined)
    • state?: 브라우저 히스토리 객체에 추가할 상태 객체 (default: undefined)
    • preventScrollReset?: true로 설정하면 <ScrollRestoration> 컴포넌트를 사용했을 경우 스크롤 위치가 초기화되지 않도록 해줌
    • relative?: 상대 경로를 계산할 때 라우트 계층 기준인지 URL 경로 기준인지를 결정 (default: route)
    • viewTransition?: 페이지 전환 시 View Transition API를 적용 (default: false)
    • unstable_defaultShouldRevalidate?: 페이지 이동 시 revalidation을 기본으로 수행할지 여부 (default: true)
    • unstable_useTransitions?: React의 startTransition을 사용하여 navigation을 Transition으로 처리 (default: false)

12. useLoaderData

현재 라우트의 loader 또는 clientLoader의 응답값을 반환한다.

import { useLoaderData } from "react-router";

export async function loader() {
  return await fakeDb.invoices.findAll();
}

export default function Invoices() {
  let invoices = useLoaderData<typeof loader>();
  // ...
}

loader의 응답값인지 clientLoader의 응답값인지 어떻게 구분할까?

그 둘을 구분할 필요가 없다. 만약 loaderclientLoader가 둘 다 정의되어 있다면 loaderclientLoader를 거쳐 최종적으로 결정된 결과값이 useLoaderData에서 반환된다.

13. useLocation

현재 상태의 Location 객체를 반환한다.

import * as React from 'react'
import { useLocation } from 'react-router'

function SomeComponent() {
  let location = useLocation()

  React.useEffect(() => {
    // Google Analytics
    ga('send', 'pageview')
  }, [location]);

  return (
    // ...
  );
}

14. useMatch

전달한 pattern과 현재 URL이 매칭된다면 PathMatch 객체를 반환하고, 매칭되지 않는다면 null을 반환한다. <NavLink> 컴포넌트처럼 현재 컴포넌트가 active한 상태인지를 파악해야 할 때 유용하게 사용할 수 있다.

import { useMatch } from "react-router";

function Navbar() {
  // 현재 URL이 "/users/123"이라면 매칭 성공!
  // match에는 { params: { id: "123" }, pathname: "/users/123", pattern: {...} }가 담깁니다.
  const match = useMatch("/users/:id");

  return (
    <nav>
      <div style={{ color: match ? "blue" : "black" }}>
        사용자 상세 정보 {match && `(ID: ${match.params.id})`}
      </div>
    </nav>
  );
}

15. useMatches

현재 활성화된 모든 라우트 객체들, 즉 루트부터 현재 라우트까지의 라우트 객체들을 담은 배열을 반환한다. 부모의 loaderData에 접근하거나 handle에 접근하여 브레드크럼을 만들어 사용할 때 유용하다.

import { useMatches } from "react-router";

function Breadcrumbs() {
  const matches = useMatches();

  return (
    <ol>
      {matches
        // handle에 crumb 정보가 있는 라우트만 필터링
        .filter((match) => Boolean(match.handle?.crumb))
        .map((match, index) => (
          <li key={index}>{match.handle.crumb}</li>
        ))}
    </ol>
  );
}

16. useNavigate

페이지 이동이 가능한 함수를 반환한다.

import { useNavigate } from "react-router";

function SomeComponent() {
  let navigate = useNavigate();
  return (
    <button onClick={() => navigate(-1)}>
      Go Back
    </button>
  );
}

반환된 함수의 params

  • to: 이동할 목적지. string 경로, To 객체, 숫자 사용 가능
  • options
    • relative?: 상대 경로를 계산할 때 라우트 계층 기준인지 URL 경로 기준인지를 결정
    • replace?: true로 설정하면 브라우저 히스토리 스택을 쌓지 않고 덮어쓰기함
    • state?: 브라우저 히스토리 객체에 추가할 상태 객체
    • flushSync?: DOM 업데이트를 React의 flushSync 함수 내부에서 실행할 지 여부 (Framework, Data 모드에서만 사용 가능)
    • preventScrollReset?: 페이지 이동 후 스크롤이 맨 위로 초기화될지 여부 (Framework, Data 모드에서만 사용 가능)
    • viewTransition?: 페이지 이동 시 View Transition API를 사용할 지 여부 (Framework, Data 모드에서만 사용 가능)
// to에 string 경로 사용
navigate("/some/route");
navigate("/some/route?search=param");

// to에 To 객체 사용
navigate({
  pathname: "/some/route",
  search: "?search=param",
  hash: "#hash",
  state: { some: "state" },
});

// to에 숫자 사용
navigate(-1); // 뒤로가기
navigate(1); // 앞으로가기

Declarative 모드와 Framework/Data 모드의 useNavigate 차이

  • Declarative
    • 렌더링 될 때마다 useNavigate에서 반환되는 함수가 새로 생성된다.
    • useNavigate에서 반환되는 함수의 리턴 타입은 void이다.
  • Framework/Data
    • 리렌더링이 발생해도 useNavigate에서 반환되는 함수가 새로 생성되지 않고 메모리 주소가 유지된다.
    • useNavigate에서 반환되는 함수의 리턴 타입은 Promise<void>이다.

따라서 다음과 같은 문제가 발생할 수 있다.

  • @typescript-eslnt/no-floating-promises에서 에러가 발생한다.
  • Framework/Data 모드에서 React.use(navigate())처럼 쓰려고 할 때 Argument of type 'void | Promise<void>' is not assignable to parameter of type 'Usable<void>’ 에러가 발생한다.

해결 방법: 지금 사용하고 있는 모드에 따라 타입 확장하기

// Declarative mode
declare module "react-router" {
  interface NavigateFunction {
    (to: To, options?: NavigateOptions): void;
    (delta: number): void;
  }
}

// Data or Framework mode
declare module "react-router" {
  interface NavigateFunction {
    (to: To, options?: NavigateOptions): Promise<void>;
    (delta: number): Promise<void>;
  }
}

17. useNavigation

현재 상태의 Navigation 객체를 반환한다. navigation.state를 사용하여 로딩 UI를 구현하거나 폼 제출 중 navigation.formData를 읽을 수도 있다.

import { useNavigation } from "react-router";

function RootLayout() {
  const navigation = useNavigation();

  // 현재 페이지 이동 중인지 확인
  const isPageLoading = navigation.state === "loading";

  return (
    <div style={{ opacity: isPageLoading ? 0.5 : 1 }}>
      {isPageLoading && <div className="spinner">로딩 중...</div>}
      
      <nav>...</nav>
      <main>
        <Outlet />
      </main>
    </div>
  );
}

18. useNavigationType

어떤 방법을 통해 현재 페이지에 도달했는지를 알려주는 NavigationType(”POP”, “PUSH”, “REPLACE”)의 문자열을 반환한다.

import { useNavigationType } from "react-router";

function MyComponent() {
  const navType = useNavigationType();

  // 사용자가 뒤로 가기로 왔는지, 링크를 눌러서 왔는지에 따라 다른 처리가 가능합니다.
  if (navType === "POP") {
    console.log("사용자가 뒤로 가기나 새로고침으로 이 페이지에 왔습니다.");
  }

  return <div>현재 접속 방식: {navType}</div>;
}

19. useOutlet

자식 라우트의 React Element를 반환한다. <Outlet> 컴포넌트의 훅 버전이라고 생각하면 된다. 복잡한 애니메이션이나 조건부 레이아웃을 처리할 때 사용한다.

import { useOutlet } from "react-router";

function Layout() {
  const outlet = useOutlet();

  return (
    <div>
      <header>헤더</header>
      {/* outlet 변수 자체가 리액트 요소이므로 바로 렌더링 가능 */}
      <div className="animation-wrapper">
        {outlet}
      </div>
    </div>
  );
}

params

  • context?: outlet에 전달할 컨텍스트

20. useOutletContext

부모 라우트에서 outlet에 전달한 context를 반환한다.

// Parent route
function Parent() {
  const [count, setCount] = React.useState(0);
  return <Outlet context={[count, setCount]} />;
}

// Child route
import { useOutletContext } from "react-router";

function Child() {
  const [count, setCount] = useOutletContext();
  const increment = () => setCount((c) => c + 1);
  return <button onClick={increment}>{count}</button>;
}

타입스크립트를 사용 중이라면 useOutletContext<ContextType>()을 호출하는 대신 부모 라우트에서 커스텀 훅을 만들어 제공하는 것이 좋다.

// Parent route
export function useUser() {
  return useOutletContext<ContextType>();
}

// Child route
function Child() {
  const { user } = useUser();
}

21. useParams

현재 URL 경로의 동적 파라미터 정보를 key/value의 객체 형태로 반환한다.

// /posts/:postId/comments/:commentId
import { useParams } from "react-router";

export default function Post() {
  let params = useParams();
  return (
    <h1>
      Post: {params.postId}, Comment: {params.commentId}
    </h1>
  );
}
// /files/*
import { useParams } from "react-router";

export default function File() {
  let params = useParams();
  let catchall = params["*"];
}

export default function File() {
  let { "*": catchall } = useParams();
  console.log(catchall);
}

22. unstable_usePrompt

useBlocker 훅을 래핑하여 window.confirm 창을 사용자에게 보여주는 훅이다.

⚠️ confirm 창이 열려 있을 때 뒤로가기 또는 앞으로 가기를 눌렀을 경우의 동작이 브라우저마다 제각각이거나 오작동할 확률이 높기 때문에 unstable_ 플래그가 영구적으로 유지될 예정이다.

unstable_usePrompt({
  message: "Are you sure?",
  when: ({ currentLocation, nextLocation }) =>
    value !== "" &&
    currentLocation.pathname !== nextLocation.pathname,
});

23. useResolvedPath

현재 위치를 기준으로 상대 경로를 절대 경로로 변환하여 반환한다. useHref와 유사하게 동작하지만 문자열이 아니라 Path 객체를 반환한다는 점이 다르다.

import { useResolvedPath } from "react-router";

function SomeComponent() {
  // if the user is at /dashboard/profile
  let path = useResolvedPath("../accounts");
  path.pathname; // "/dashboard/accounts"
  path.search; // ""
  path.hash; // ""
}

24. useRevalidator

수동으로 데이터의 revalidation을 실행한다. window focus, polling(채팅 알림, 주식 시세처럼 주기적으로 호출) 같은 경우에 사용하고 일반적인 CRUD 작업에는 useRevalidator를 사용하지 않는 것이 좋다.

import { useRevalidator } from "react-router";

function WindowFocusRevalidator() {
  const revalidator = useRevalidator();

  useFakeWindowFocus(() => {
    revalidator.revalidate();
  });

  return (
    <div hidden={revalidator.state === "idle"}>
      Revalidating...
    </div>
  );
}

returns

  • revalidate: 재검증 실행 함수
  • state: 현재 재검증 상태

25. useRouteError

route module의 ErrorBoundary 내부에서 action, loader, 컴포넌트 렌더링 중 발생한 에러에 접근한다.

export function ErrorBoundary() {
  const error = useRouteError();
  return <div>{error.message}</div>;
}

26. useRouteLoaderData

특정 라우트의 ID를 사용하여 해당 라우트의 loaderData에 접근한다.

import { useRouteLoaderData } from "react-router";

function SomeComponent() {
  const { user } = useRouteLoaderData("root");
}

// You can also specify your own route ID's manually in your routes.ts file:
route("/", "containers/app.tsx", { id: "app" })
useRouteLoaderData("app");

27. useRoutes

<Routes> 컴포넌트의 훅 버전으로, route tree로 사용할 수 있는 React element를 생성한다.

import { useRoutes } from "react-router";

function App() {
  let element = useRoutes([
    {
      path: "/",
      element: <Dashboard />,
      children: [
        {
          path: "messages",
          element: <DashboardMessages />,
        },
        { path: "tasks", element: <DashboardTasks /> },
      ],
    },
    { path: "team", element: <AboutPage /> },
  ]);

  return element;
}

params

  • routes: 라우트 계층 구조를 정의한 RouteObject 배열
  • locationArg?: 현재 Location 대신 사용할 Location 객체 또는 pathname

28. useSearchParams

현재 URL의 URLSearchParams와 이를 업데이트하는 함수를 배열로 반환한다. 즉, [searchParams, setSearchParams]를 반환한다.

let [searchParams, setSearchParams] = useSearchParams();

// a search param string
setSearchParams("?tab=1");

// a shorthand object
setSearchParams({ tab: "1" });

// object keys can be arrays for multiple values on the key
setSearchParams({ brand: ["nike", "reebok"] });

// an array of tuples
setSearchParams([["tab", "1"]]);

// a `URLSearchParams` object
setSearchParams(new URLSearchParams("?tab=1"));

// function callback update
setSearchParams((searchParams) => {
  searchParams.set("tab", "2");
  return searchParams;
});

params

  • defaultInit?: 초기값. 하지만 첫 렌더링 시에는 URL에 반영되지 않는다.

⚠️ useState의 함수형 업데이트는 여러 번 호출 시 이전 호출의 결과를 기다려주는 queueing 로직이 적용되어 있지만 setSearchParams의 함수형 업데이트는 그렇지 않다. 이 동작을 구현하기 위해서는 setState를 추가로 사용하거나 한 번의 호출 안에 모든 업데이트를 넣어야 한다.

// ❌ 잘못된 방법 (각각 호출)
setSearchParams({ a: 1 });
setSearchParams({ b: 2 });

// ✅ 올바른 방법 (한 번에 호출)
setSearchParams(prev => {
  const next = new URLSearchParams(prev);
  next.set("a", "1");
  next.set("b", "2");
  return next;
});

// ✅ 올바른 방법 (useState 사용)
const [searchParams, setSearchParams] = useSearchParams();

const [localParams, setLocalParams] = useState(
  () => new URLSearchParams(searchParams)
);

useEffect(() => {
  setSearchParams(localParams, { replace: true });
}, [localParams, setSearchParams]);

⚠️ searchParams는 Stable Reference이므로, 컴포넌트가 리렌더링되어도 메모리 주소가 변경되지 않는다. 따라서 useEffect의 의존성 배열에 넣어도 안전하게 사용할 수 있다. 하지만 searchParams 내부의 값은 .set()이나 .append()를 사용하면 변경될 수 있기 때문에 이렇게 변경한다면 데이터는 변경되지만 URL은 변경되지 않아 문제가 발생할 수 있다. 따라서 제대로 사용하기 위해서는 URLSearchParams 객체 자체를 업데이트해야 한다.

// ❌ 위험한 코드 (Mutable 수정)
const handleClick = () => {
  // 1. 객체를 직접 수정 (주소창은 안 바뀜!)
  searchParams.set("tab", "2"); 
 
  // 2. 다른 상태를 바꿈 -> 리렌더링 유발
  setSomeOtherState(prev => prev + 1); 
 
  // 결과: URL은 그대로인데 화면은 탭 2로 바뀔 수 있음 (데이터 불일치 발생)
};

// ✅ 권장되는 코드 (Immutable 방식)
const handleClick = () => {
  const newParams = new URLSearchParams(searchParams);
  newParams.set("tab", "2");
  setSearchParams(newParams); // 주소창과 리액트 상태를 동시에 업데이트!
};

29. useSubmit

수동으로 Form을 제출한다.

import { useSubmit } from "react-router";

function SomeComponent() {
  const submit = useSubmit();
  return (
    <Form onChange={(event) => submit(event.currentTarget)} />
  );
}

30. useViewTransitionState

현재 페이지에서 특정 페이지로 활성화된 View Transition이 있으면 true를 반환한다. <Link>, <Form>, useNavigate, useSubmit에서 viewTransition 옵션이 활성화되어있어야 제대로 동작한다.

import { useViewTransitionState } from "react-router";

function ImageItem({ id, src }) {
  // "/photos/1" 경로로 뷰 트랜지션이 진행 중인지 확인
  const isTransitioning = useViewTransitionState(`/photos/${id}`);

  return (
    <div
      style={{
        // 트랜지션 중일 때만 특별한 view-transition-name을 부여
        viewTransitionName: isTransitioning ? "selected-photo" : "none",
        opacity: isTransitioning ? 0.5 : 1,
      }}
    >
      <img src={src} alt="Photo" />
    </div>
  );
}

params

  • to: 이동할 목적지 주소
  • options.relative?: 경로 계산 기준이 route인지 path인지 결정
profile
프론트엔드 개발자

0개의 댓글