Communitygithub.com

react-patterns

React 18/19 패턴: 훅 규율, 서버/클라이언트 컴포넌트 경계, Suspense + 오류 경계, 폼 액션, 데이터 페칭, 상태 관리 결정 트리, 접근성 우선 컴포지션. React 컴포넌트 작성 또는 검토 시 사용합니다.

react-patterns란 무엇인가요?

이 스킬은 React 18 및 19에서 권장되는 최신 패턴을 종합적으로 제공하여, 견고하고 접근 가능하며 성능이 우수한 컴포넌트 트리를 구축할 수 있도록 돕습니다. 주요 내용으로는 훅 사용 규율(useEffect 정리, 메모이제이션 최소화), 상태 관리 결정 트리(local state에서 외부 스토어까지), 서버/클라이언트 컴포넌트 경계 설정, Suspense와 오류 경계를 활용한 점진적 콘텐츠 공개, React 19 form actions와 useOptimistic을 활용한 현대적 폼 처리, TanStack Query/SWR/RSC를 활용한 데이터 페칭 패턴 등을 다룹니다. React Router, Next.js App Router, TanStack Router 등 라우터에 구애받지 않으며, React Native나 Next.js 세부 사항은 범위에 포함하지 않습니다. 이 스킬은 코드 리뷰, 리팩토링, 새 컴포넌트 설계 시 즉시 적용 가능한 실용적인 레시피와 예제를 제공하여 개발자의 생산성을 높이고 일관된 코드베이스를 유지하는 데 도움을 줍니다.

지원 대상✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/affaan-m/everything-claude-code/tree/main/skills/react-patterns

즐겨 사용하는 AI에게 물어보기

이 에이전트 스킬이 미리 로드된 새 채팅을 엽니다.

문서

React 패턴

견고하고 접근 가능하며 성능이 우수한 컴포넌트 트리를 구축하기 위한 관용적인 React 18/19 패턴입니다.

활성화 시점

  • React 함수 컴포넌트, 커스텀 훅, 또는 컴포넌트 트리를 작성하거나 수정할 때
  • JSX/TSX 파일을 검토할 때
  • 상태 구조나 컴포넌트 구성을 설계할 때
  • 클래스 컴포넌트 또는 forwardRef/useEffect가 많은 레거시 코드를 마이그레이션할 때
  • 로컬 상태, 끌어올린 상태, 컨텍스트, 외부 스토어 중에서 선택해야 할 때
  • 서버 컴포넌트 / 클라이언트 컴포넌트(Next.js App Router, RSC)로 작업할 때
  • React 19 액션 또는 제어 입력을 사용하여 폼을 구현할 때
  • TanStack Query / SWR / RSC로 데이터 페칭을 구성할 때

핵심 원칙

1. 렌더는 Props와 State의 순수 함수입니다

// 좋은 예: 렌더 중에 파생
function Cart({ items }: { items: CartItem[] }) {
  const total = items.reduce((sum, i) => sum + i.price * i.qty, 0);
  return <span>{formatMoney(total)}</span>;
}

// 나쁜 예: 파생 상태를 별도로 저장
function Cart({ items }: { items: CartItem[] }) {
  const [total, setTotal] = useState(0);
  useEffect(() => {
    setTotal(items.reduce((sum, i) => sum + i.price * i.qty, 0));
  }, [items]);
  return <span>{formatMoney(total)}</span>;
}

useEffect 안에 파생 상태를 두면 렌더 사이클이 추가되고, 동기화가 깨질 수 있으며, 데이터 흐름이 불명확해집니다.

2. 부수 효과는 렌더 외부에서 수행합니다

이펙트, 변이, 네트워크 호출, 구독은 이벤트 핸들러나 useEffect 안에 위치해야 하며, 절대 렌더 본문 안에 두지 않습니다.

3. 상속보다는 합성을 사용합니다

React는 컴포넌트에 대한 상속 모델이 없습니다. children, render props, 또는 컴포넌트 props를 사용하여 합성합니다.

훅 규율

전체 규칙은 rules/react/hooks.md를 참조하세요. 주요 내용:

  • 최상위 레벨에서만 호출, 조건부로 호출 금지
  • 모든 구독, 인터벌, 리스너는 정리(cleanup) 필수
  • 새 상태가 이전 상태에 의존하는 경우 함수형 업데이터 사용 (setX(prev => prev + 1))
  • 기본 원칙: 메모이제이션하지 않음 — 프로파일러나 의존성 체인이 중요함을 증명할 때만 useMemo/useCallback 추가
  • 동일한 훅 시퀀스가 2개 이상의 컴포넌트에 나타날 때만 커스텀 훅 추출

상태 위치 결정 트리

하나의 컴포넌트에서만 사용?
  -> 그 컴포넌트 안에 useState 사용

부모 + 몇몇 하위 컴포넌트에서 사용?
  -> 가장 가까운 공통 조상으로 끌어올리기

먼 분기에서 사용되며 읽기 빈도가 낮음 (테마, 인증, 로케일)?
  -> React Context

트리 전체에서 공유되며 업데이트 빈도가 높음?
  -> 외부 스토어 (Zustand, Jotai, Redux Toolkit)

서버에서 파생됨?
  -> 서버 상태 라이브러리 (TanStack Query, SWR, RSC fetch)

대부분의 페이지는 컨텍스트나 전역 스토어가 필요하지 않습니다. 끌어올리기가 반복되어 고통스러워질 때까지는 추상화를 자제하세요.

서버 / 클라이언트 컴포넌트 (RSC)

// 서버 컴포넌트 - 기본값, async, 자체적으로 JS를 배포하지 않음
export default async function ProductPage({ params }: { params: { id: string } }) {
  const product = await db.product.findUnique({ where: { id: params.id } });
  if (!product) notFound();
  return <ProductView product={product} />;
}

// 클라이언트 컴포넌트 - "use client"로 선택
"use client";
export function AddToCartButton({ productId }: { productId: string }) {
  const [pending, startTransition] = useTransition();
  return (
    <button
      disabled={pending}
      onClick={() => startTransition(() => addToCart(productId))}
    >
      {pending ? "Adding..." : "Add to cart"}
    </button>
  );
}

경계:

  • 서버 -> 클라이언트: 직렬화 가능한 props 또는 children 전달
  • 클라이언트 -> 서버: <form action={...}>를 통해 서버 액션 호출 또는 이벤트 핸들러에서 명령형으로 호출
  • 클라이언트 컴포넌트 파일에서 서버 컴포넌트를 import하지 말고, children을 통해 합성하세요.

Suspense + 오류 경계

<ErrorBoundary fallback={<ErrorView />}>
  <Suspense fallback={<UserSkeleton />}>
    <UserDetail id={id} />
  </Suspense>
</ErrorBoundary>
  • Suspense 경계는 라우트 루트가 아닌 데이터에 가깝게 배치하여 콘텐츠를 점진적으로 공개합니다.
  • 오류 경계는 여전히 클래스 API입니다. 훅 친화적인 래퍼를 위해 react-error-boundary를 사용하세요.
  • 경계는 자식의 렌더, 라이프사이클, 생성자에서 발생한 오류만 캐치합니다. 이벤트 핸들러나 비동기 코드의 오류는 캐치하지 않습니다.

폼

React 19 form actions (새 코드에 권장)

"use client";
import { useActionState } from "react";

const initial = { error: null as string | null };

async function updateUserAction(_prev: typeof initial, formData: FormData) {
  "use server";
  const parsed = UserSchema.safeParse(Object.fromEntries(formData));
  if (!parsed.success) return { error: "Invalid input" };
  await db.user.update({ where: { id: parsed.data.id }, data: parsed.data });
  return { error: null };
}

export function UserForm() {
  const [state, formAction, pending] = useActionState(updateUserAction, initial);
  return (
    <form action={formAction}>
      <input name="name" required />
      <button type="submit" disabled={pending}>Save</button>
      {state.error && <p role="alert">{state.error}</p>}
    </form>
  );
}

제어 입력 (Controlled inputs)

값이 다른 UI를 구동하거나, 키 입력마다 형식을 지정하거나, 실시간 유효성 검사를 구현할 때는 제어 입력을 사용하세요.

복잡한 폼

다단계 폼, 동적 필드 배열, 또는 교차 필드 유효성 검사가 필요하면 라이브러리(React Hook Form, TanStack Form)를 사용하세요. 단순한 수준을 넘는 폼의 상태 관리를 직접 구현하는 것은 유지보수의 함정입니다.

데이터 페칭 결정 매트릭스

필요 사항도구
Next.js App Router에서 요청별 데이터RSC await fetch()
클라이언트 측 캐시 + 변이 + 무효화TanStack Query
가벼운 클라이언트 캐시 + 재검증SWR
실시간 구독Server-Sent Events, WebSockets, 또는 라이브러리의 구독 API
일회성 fire-and-forget이벤트 핸들러에서 fetch()

애플리케이션 데이터에 useEffect + fetch를 사용하지 마세요. 경쟁 조건, 캐시 부재, 재시도 없음, Suspense 통합 없음 등의 문제가 있습니다.

합성 레시피

Slot via children

<Layout>
  <Header />
  <Main>{content}</Main>
</Layout>

명명된 슬롯

<Page header={<Nav />} sidebar={<Filters />}>
  <Results />
</Page>

복합 컴포넌트 (Context를 통한 공유 상태)

<Tabs defaultValue="profile">
  <Tabs.List>
    <Tabs.Trigger value="profile">Profile</Tabs.Trigger>
    <Tabs.Trigger value="settings">Settings</Tabs.Trigger>
  </Tabs.List>
  <Tabs.Panel value="profile"><Profile /></Tabs.Panel>
  <Tabs.Panel value="settings"><Settings /></Tabs.Panel>
</Tabs>

Render prop / function-as-child

부모가 렌더링된 출력에 매개변수를 전달해야 할 때 유용합니다:

<DataLoader id={id}>
  {({ data, isLoading }) => isLoading ? <Spinner /> : <UserCard user={data} />}
</DataLoader>

현대적인 대안: 동일한 형태를 반환하는 훅(useData(id)) — 일반적으로 더 깔끔합니다.

성능

React.memo가 실제로 도움이 되는 경우

다음 조건을 모두 만족할 때만 컴포넌트를 React.memo로 감싸세요:

  1. 자주 리렌더링됨
  2. props가 렌더링 사이에 일반적으로 동일함
  3. 렌더링 비용이 측정 가능할 정도로 큼

React.memo는 모든 렌더링마다 동등성 검사를 추가합니다. props가 대부분의 렌더링에서 다르다면, 이 검사는 순수한 오버헤드입니다.

렌더링 폭포 방지

  • 가능하면 상태를 위로 끌어올리지 말고 아래로 내리세요.
  • 컨텍스트 분할: 관심사별로 하나의 컨텍스트를 만들어 themeContext의 변경이 auth 소비자를 리렌더링하지 않도록 합니다.
  • 외부 상태 라이브러리에는 useSyncExternalStore를 사용하세요. 안전한 동시성 렌더링에 필수입니다.

리스트

  • 안정적인 key props 제공 (데이터베이스 id, 배열 인덱스 아님)
  • 표시되는 항목 수가 ~50개를 넘고 각 행이 단순하지 않으면 @tanstack/react-virtual 또는 react-window로 긴 목록을 가상화하세요.

접근성 우선 합성

  • role 속성을 사용하기 전에 항상 의미론적 HTML(<button>, <a>, <nav>, <main>)을 렌더링하세요.
  • 모든 상호작용 요소는 키보드로 접근 가능해야 합니다.
  • 폼 입력에는 레이블이 필요합니다. <label htmlFor> 또는 아이콘으로 시각적 레이블이 제공된 경우 aria-label을 사용하세요.
  • 라우트 변경 및 모달 열기/닫기 시 포커스를 관리하세요.
  • 컴포넌트 테스트에서 axe를 실행하세요(skills/react-testing 참조).
  • 참조 링크: skills/accessibility/SKILL.md에서 WCAG 기준 및 패턴 라이브러리를 다룹니다.

라우팅

이 스킬은 라우터에 구애받지 않습니다. 위 패턴은 React Router, TanStack Router, Next.js App Router, Remix Router에서 모두 작동합니다. 라우터별 패턴(로더, 액션, 중첩 레이아웃)은 해당 라우터의 문서를 따르세요. 이러한 패턴은 React 코어 위에 계층화된 프레임워크 관심사입니다.

범위 외 (포인터 섹션)

  • Next.js 세부 사항: App Router 데이터 로딩, Route Handlers, Middleware, Parallel Routes — 별도 관심사, Next.js 문서 사용
  • React Native: 플랫폼별 패턴이 크게 달라 별도의 react-native-patterns 스킬이 필요합니다 (아직 없음)
  • Remix: Loader/action 규칙은 RSC와 겹치지만 Remix 문서를 따르세요.

관련

  • 규칙: rules/react/ — 코딩 스타일, 훅, 패턴, 보안, 테스팅
  • 스킬: react-performance (Vercel 기반 성능 규칙), frontend-patterns (프레임워크 간 UI 관심사), accessibility, angular-developer (프레임워크 비교)
  • 에이전트: react-reviewer (코드 리뷰), react-build-resolver (빌드/번들러 오류)
  • 명령어: /react-review, /react-build, /react-test

예제

디바운스 검색을 위한 커스텀 훅

function useDebounce<T>(value: T, delay = 300): T {
  const [debounced, setDebounced] = useState(value);
  useEffect(() => {
    const id = setTimeout(() => setDebounced(value), delay);
    return () => clearTimeout(id);
  }, [value, delay]);
  return debounced;
}

function SearchBox() {
  const [query, setQuery] = useState("");
  const debounced = useDebounce(query, 300);
  const { data } = useQuery({
    queryKey: ["search", debounced],
    queryFn: () => searchApi(debounced),
    enabled: debounced.length > 0,
  });
  return (
    <>
      <input value={query} onChange={(e) => setQuery(e.target.value)} />
      <Results items={data ?? []} />
    </>
  );
}

React 19 useOptimistic을 활용한 낙관적 UI

"use client";
import { useOptimistic } from "react";

export function MessageList({ messages }: { messages: Message[] }) {
  const [optimistic, addOptimistic] = useOptimistic(
    messages,
    (state, newMessage: Message) => [...state, newMessage],
  );

  async function send(formData: FormData) {
    const text = String(formData.get("text"));
    addOptimistic({ id: "pending", text, sender: "me" });
    await saveMessage(text);
  }

  return (
    <>
      <ul>{optimistic.map((m) => <li key={m.id}>{m.text}</li>)}</ul>
      <form action={send}>
        <input name="text" />
        <button type="submit">Send</button>
      </form>
    </>
  );
}

렌더 폭포를 방지하기 위한 컨텍스트 분할

// 두 개의 컨텍스트: 하나는 거의 변경되지 않고, 다른 하나는 자주 변경됨
const ThemeContext = createContext<Theme>("light");
const NotificationsContext = createContext<Notification[]>([]);

// ThemeContext만 소비하는 컴포넌트는 알림이 변경되어도 리렌더링되지 않음

Individual skills in this repo

This repo contains 20 individual skills — each has its own dedicated page.

accessibility

Design, implement, and audit inclusive digital products using WCAG 2.2 Level AA. Use when building or auditing UI that must meet WCAG 2.2 Level AA, or when reviewing a change for keyboard, contrast, or screen-reader support.

affaan-m/claude-api

Anthropic Claude API patterns for Python and TypeScript. Covers Messages API, streaming, tool use, vision, extended thinking, batches, prompt caching, and Claude Agent SDK. Use when building applications with the Claude API or Anthropic SDKs.

affaan-m/everything-claude-code

End-to-end marketing campaign planning and execution. Covers audience research, positioning, campaign angle definition, landing page copy, email sequences, social posts, ad copy, short-form video scripts, and content calendars. Use as the orchestration layer for multi-channel product launches. Use when planning or executing a multi-channel product launch, or producing landing page, email, social, or ad copy.

affaan-m/everything-claude-code

Development conventions and patterns for everything-claude-code. JavaScript project with conventional commits.

affaan-m/everything-claude-code-conventions

Development conventions and patterns for everything-claude-code. JavaScript project with conventional commits.

affaan-m/frontend-design

Create distinctive, production-grade frontend interfaces with high design quality. Use when the user asks to build web components, pages, or applications and the visual direction matters as much as the code quality.

affaan-m/gget

gget CLI and Python workflow for quick genomic database queries, sequence lookup, BLAST-style searches, enrichment checks, and reproducible bioinformatics evidence logs.

affaan-m/literature-review

Systematic literature-review workflow for academic, biomedical, technical, and scientific topics, including search planning, source screening, synthesis, citation checks, and evidence logging.

affaan-m/motion-ui

Production-ready UI motion system for React/Next.js. Use when implementing animations, transitions, or motion patterns.

affaan-m/project-guidelines-example

Example project-specific skill template based on a real production application.

affaan-m/pubmed-database

Direct PubMed and NCBI E-utilities search workflows for biomedical literature, MeSH queries, PMID lookup, citation retrieval, and API-backed literature monitoring.

affaan-m/scholar-evaluation

Structured scholarly-work evaluation for papers, proposals, literature reviews, methods sections, evidence quality, citation support, and research-writing feedback.

affaan-m/uspto-database

USPTO patent and trademark data workflow for official record lookup, PatentSearch queries, TSDR checks, assignment data, and reproducible IP research logs.

agent-architecture-audit

Full-stack diagnostic for agent and LLM applications. Audits the 12-layer agent stack for wrapper regression, memory pollution, tool discipline failures, hidden repair loops, and rendering corruption. Produces severity-ranked findings with code-first fixes. Essential for developers building agent applications, autonomous loops, or any LLM-powered feature. Use when an agent or LLM feature misbehaves and the failing layer is unknown, or before shipping an agent stack.

agent-eval

Head-to-head comparison of coding agents (Claude Code, Aider, Codex, etc.) on custom tasks with pass rate, cost, time, and consistency metrics. Use when choosing between coding agents, or when a change to an agent setup needs measured pass rate, cost, and time rather than an impression.

agent-harness-construction

Design and optimize AI agent action spaces, tool definitions, and observation formatting for higher completion rates. Use when defining or revising an agent

agentic-engineering

Operate as an agentic engineer using eval-first execution, decomposition, and cost-aware model routing. Use when planning or executing engineering work that agents will carry out end to end.

agentic-os

Build persistent multi-agent operating systems on Claude Code. Covers kernel architecture, specialist agents, slash commands, file-based memory, scheduled automation, and state management without external databases. Use when building a persistent multi-agent system on Claude Code with its own memory, commands, and scheduling.

agent-introspection-debugging

Structured self-debugging workflow for AI agent failures using capture, diagnosis, contained recovery, and introspection reports. Use when an agent run fails and you need a reproducible diagnosis instead of a retry.

agent-payment-x402

Add x402 payment execution to AI agents with per-task budgets, spending controls, and non-custodial wallets. Supports Base through agentwallet-sdk and X Layer through OKX Payments / OKX Agent Payments Protocol. Use when an agent must pay for something itself and needs per-task budgets, spending controls, and a non-custodial wallet.

관련 스킬