REMIX 사용법 정리 v2.4

share · 2024-8-5

← 리스트로

Remix 사용법 정리 v2.4

Remix 프레임웍 사용의 킥오프를 위해, 사용법을 간단하게 공유한다.

자주 사용할것 같은 내용만 정리하였다. 더 자세한 사용법은 공식문서 참고

  • 목차
    • 라우팅 파일규칙
    • 라우터 모듈
      • loader 함수
      • action 함수
      • clientLoader 함수
      • clientAction 함수
    • 리액트 훅
      • useLoaderData
      • useActionData
      • useSubmit
      • useRevalidator
      • useParams
      • useNavigate
    • 컴포넌트
      • Form
      • Link
      • Outlet
    • 액션 실행하는법(Form vs fetcher.Form)

라우팅 파일규칙

routes 폴더 밑에 있는 파일명에 모든 라우팅 정보를 포함한다. 닷(.)을 구분자로 하여 페이지 깊으를 표현한다.

문서에서 많은 내용을 봤지만, 그 중에서도 대략 더 중요해 보이는 규칙을 아래와 같이 정리하였다.

  • 특정 아이템 앞에 언더바가 있을경우 url규칙에서 무시된다.
  • 특정 아이템 뒤에 언더바가 있을경우 layout규칙에서 무시된다.
  • 특정 아이템에 $변수명 표현이 있을경우 동적 세그먼트로 동작한다.
app/
├── routes/
│   ├── _index.tsx
│   ├── _auth.tsx
│   ├── _auth.login.tsx
│   ├── about.tsx
│   ├── concerts._index.tsx
│   ├── concerts.trending.tsx
│   ├── concerts_.menu2.tsx
│   ├── concerts.$city.tsx
└── root.tsx
URL PATH LAYOUT
/ app/routes/_index.tsx app/root.tsx
app/routes/_auth.tsx app/root.tsx
/login app/routes/_auth.login.tsx app/routes/_auth.tsx
/about app/routes/about.tsx app/root.tsx
/concerts app/routes/concerts._index.tsx app/routes/concerts.tsx -> app/root.tsx
/concerts/trending app/routes/concerts.trending.tsx app/routes/concerts.tsx -> app/root.tsx
/concerts/menu2 app/routes/concerts_.menu2.tsx app/root.tsx
/concerts/seoul app/routes/concerts.$city.tsx app/routes/concerts.tsx

라우터 모듈

loader 함수

  • 사용자가 페이지에 GET으로 접근할때 실행
  • GET(loader) -> RENDER

loader 함수가 먼저 실행되어 데이터를 만든 후, 페이지가 그려진다. loader 함수는 node서버에서 실행된다.

// 사용 예
export async function loader() {
  return json(await prisma.user.findMany());
  // return json(await fetch(...));
}

action 함수

  • 사용자가 페이지에 GET 이외에 method로 접근할때 실행
  • POST|PUT|DELETE(action) -> GET(loader) -> RENDER

action 함수에서 데이터를 업데이트 시킨후 loader함수에서 데이터를 다시 만들고 페이지가 업데이트된다. action 은 함수도 node서버에서 실행된다.

export async function action({
  request,
}: ActionFunctionArgs) {
  const body = await request.formData(); // 클라이언트로부터 전달받은 폼데이터
  const todo = await fakeCreateTodo({ // 폼데이터를 이용해서 api서버에 직접 패치
    title: body.get("title"),
  });

  // return json({ message: `Hello, ${name}` }); // 데이터 업데이트 후 useActionData로 값 전달
  return redirect(`/todos/${todo.id}`); // 데이터 업데이트 후 리다이랙션
}

clientLoader 함수

loader 함수를 node서버에서 실행하지 않고 브라우저에서 실행해 준다. 다만 loader가 없고 clientLoader만 구현되어 있다면 해당 라우팅에 속한 컴포넌트는 ssr을 하지 않는다.

loader와 clientLoader를 전부구현하였다면, 페이지에 처음접근했을때만 loader로 부터 서버에서 데이터를 만들고 그 이후부터 페이지를 갱신할때는 clientLoader로 그린다.

clientAction 함수

action 함수를 node서버에서 실행하지 않고 브라우저에서 실행해 준다.

리액트 훅

useLoaderData

가장 가까운 경로의 loader에서 반환된 값을 컴포넌트에서 받아 사용한다

import { json } from "@remix-run/node";
import { useLoaderData } from "@remix-run/react";

// 보통 특정 페이지 라우터의 컴포넌트 바로  위에 정의한다.
export async function loader() {
  return json(await fetch(...));
}

export default function Component() {
  const datas = useLoaderData<typeof loader>();
  // ...
}

useActionData

해당 경로에서 가장 최근에 action에서 처리후 반환한 값을 컴포넌트에서 받아 사용한다. action을 사용한적이 없다면 undefined가 반환된다.

import type { ActionFunctionArgs } from "@remix-run/node"; // or cloudflare/deno
import { json } from "@remix-run/node"; // or cloudflare/deno
import { Form, useActionData } from "@remix-run/react";

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

  // return redirect("/");
}

export default function Invoices() {
  const data = useActionData<typeof action>();
  // Form post로 설정한후 submit이 되면 액션이 실행됨
  return (
    <Form method="post">
      <input type="text" name="visitorsName" />
      {data?.errorMessage : ""}
    </Form>
  );
}

useSubmit

Form 컴포넌트를 사용하지 않고 action을 실행하고 싶을때 useSubmit훅을 사용한다.

import { useRevalidator, useLoaderData, useSubmit } from "@remix-run/react";

export default Function ComponentPage() {
    const submit = useSubmit();

    const handleDelete = (seq) => {
        submit({ seq }, { method: "DELETE" });
    };
    const handleAdd = (seq) => {
        submit({ seq }, { method: "POST" });
    };
    const handleUpdate = (seq) => {
        submit({ seq }, { method: "PUT" });
    };
    const handleSearch = (seq) => {
        submit({ seq }, { method: "GET" });
    };
    ...
}

useRevalidator

loader로 부터 다시 페이지를 그리고 싶을때 사용한다.

import { useRevalidator } from "@remix-run/react";

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

  const refresh = () => {
    revalidator.revalidate();
  };

  return (
    <>
        <button 페이지 데이터 갱신 </button>
        <div hidden={revalidator.state === "idle"}>
          Revalidating...
        </div>
    </>
  );

useParams

라우팅 규칙이 동적인 경우 app/routes/concerts.$city.tsx 에서 city값을 받아서 사용할수 있다.

// 액션에서
export const loader = async ({
  params,
}: LoaderFunctionArgs) => {
  console.log(params.city);
};

// 컴포넌트에서
export default function UserComponent() {
  const param = useParams();
  console.log('PARAM', param.city);
}

useNavigate

<button => { navigate('/about'); }}>
  어바웃 페이지로 이동하기
</button>

<button => { navigate(-1); }} >
이전 페이지로 이동하기
</button>

컴포넌트

Form

submit을 이용해 액션을 호출하기위한 컴포넌트

<Form>
    <input type="text" name="value" value="7" / >
</Form>

fetcher.Form

url 변경이 필요없이 특정 부분만 새로운 데이터로 갱신하고 싶을경우 변경이 페이지 history에 영향을 주면 안되는 경우

<fetcher.Form>
    <input type="text" name="value" value="7" / >
</fetcher.Form>

Link

a 태그 대신 페이지 이동

<Link
  to={{
    pathname: "/some/path",
    search: "?query=string",
    hash: "#hash",
  }}
/>

Outlet

하위 페이지에 레이아웃을 그려준다.

import { Outlet } from "@remix-run/react";

export default function SomeParent() {
  return (
    <div>
      <h1>Parent Content</h1>

      <Outlet />
    </div>
  );
}

액션 실행하는법(Form vs fetcher.Form)

Form 이나 useSubmit 의 submit으로 액션을 실행할때

  • app/routes/pageA.tsapp/routes/pageB.ts 가 있다고 가정
  • pageA 에서 submit({ data }, { action: '/pageB', method: 'POST' });을 실행
  • pageB에 있는 action이 실행된 후 pageB에 있는 loader가 실행되고 pageB를 화면에 보여줌

fetcher.Form 이나 fetcher.submit 으로 액션 실행할때

  • app/routes/pageA.tsapp/routes/pageB.ts 가 있다고 가정
  • pageA 에서 fetcher.submit({ data }, { action: '/pageB', method: 'POST' });을 실행
  • pageB에 있는 action이 실행된 후 pageA에 있는 loader가 실행되고 pageA를 현재 그대로 화면에 보여줌