本文へ移動
cccskills
無料GitHub で公開

react-conventions

React component structure, hooks rules, file naming, project layout, composition patterns, performance idioms, and effects discipline. Apply when implementing or modifying React SPA code. Use this skill to: - Structure a new component or feature folder. - Apply hooks correctly (rules, naming, dependency arrays). - Compose components via children, render props, or compound patterns. - Write effects only when needed (and avoid common misuses). - Pick performance escape hatches (memo, useMemo, useCallback) when justified. Do NOT use this skill for: - State management lib choice (see react-state-management). - Routing primitives (see react-routing). - Form patterns (see react-forms). - Testing (see react-testing).

インストール方法を見る

含まれるファイル(1)

  • SKILL.md10.6 KB

SKILL.md(原文)

インストールする前に、エージェントに与えられる指示の中身を確認できます。

React Conventions

This skill consolidates idioms that hold across React SPA projects. Apply alongside js-foundation:typescript-patterns (general TS strictness).

Project layout

Two common structures:

Feature-based (preferred for medium+ apps)

src/
├── main.tsx                       # entry — ReactDOM.createRoot
├── App.tsx                         # root component
├── routes.tsx                      # route definitions (or routes/ folder)
├── features/
│   ├── users/
│   │   ├── UserList.tsx
│   │   ├── UserDetail.tsx
│   │   ├── UserForm.tsx
│   │   ├── api/
│   │   │   └── users.ts            # fetcher / TanStack Query hooks
│   │   ├── hooks/
│   │   │   └── useUsers.ts
│   │   └── types.ts
│   └── orders/
│       └── ...
├── components/
│   ├── ui/                         # primitives (Button, Input, Modal)
│   └── shared/                     # cross-feature shared components
├── lib/                            # framework-agnostic utilities
│   ├── http.ts
│   └── format.ts
├── hooks/                          # cross-feature hooks (useDebounce, useMediaQuery)
├── styles/                         # global CSS, design tokens
└── types/                          # global types

Type-based (acceptable for small apps)

src/
├── components/
├── hooks/
├── pages/                          # route components
├── lib/
└── styles/

Mirror what exists. Don't refactor structure as part of feature work.

File naming

WhatConvention
Component filePascalCase.tsx (UserCard.tsx)
Hook fileuseCamelCase.ts (useDebounce.ts)
Utility modulekebab-case.ts or camelCase.ts (match project)
Test file*.test.tsx colocated, OR mirror in tests/
Story file*.stories.tsx colocated (Storybook)
Type filetypes.ts per feature, OR *.types.ts

Hooks rules

// ✅ Top-level only
function MyComponent() {
  const [count, setCount] = useState(0);
  const ref = useRef(null);
  useEffect(() => { /* ... */ }, []);
  return <div />;
}

// ❌ Conditional hook
function Bad() {
  if (someCondition) {
    const [x, setX] = useState(0); // breaks Rules of Hooks
  }
}

// ❌ Hook in loop
function AlsoBad() {
  for (let i = 0; i < 5; i++) {
    useState(0); // breaks
  }
}

Custom hooks always start with use*:

// src/hooks/useDebounce.ts
import { useEffect, useState } from 'react';

export 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;
}

The use* prefix is what enables the linter and React itself to recognize it as a hook.

Dependency arrays

Trust the eslint-plugin-react-hooks exhaustive-deps rule. If it flags, fix the dep array — don't disable the rule.

If a function is recreated each render but its identity matters (used in effect dep array, or as prop to memoized child):

// Stabilize the function reference
const handleClick = useCallback((id: string) => {
  doSomething(id);
}, []); // deps: anything closed over from outer scope

Same for objects:

const config = useMemo(() => ({ retries: 3, timeout: 5000 }), []);

But: don't pre-emptively wrap everything in useCallback/useMemo. Only when the deps array failure or memoization break is real.

Component patterns

Plain functional component

type Props = {
  title: string;
  onClose: () => void;
  children: React.ReactNode;
};

export function Modal({ title, onClose, children }: Props) {
  return (
    <div role="dialog" aria-labelledby="modal-title">
      <h2 id="modal-title">{title}</h2>
      {children}
      <button onClick={onClose}>Close</button>
    </div>
  );
}

Compound components (shared context)

// Tabs.tsx
import { createContext, useContext, useState } from 'react';

type TabsContext = { active: string; setActive: (s: string) => void };
const Ctx = createContext<TabsContext | null>(null);

export function Tabs({ defaultTab, children }: { defaultTab: string; children: React.ReactNode }) {
  const [active, setActive] = useState(defaultTab);
  return <Ctx.Provider value={{ active, setActive }}>{children}</Ctx.Provider>;
}

export function Tab({ name, children }: { name: string; children: React.ReactNode }) {
  const ctx = useContext(Ctx);
  if (!ctx) throw new Error('Tab must be inside Tabs');
  return (
    <button onClick={() => ctx.setActive(name)} aria-selected={ctx.active === name}>
      {children}
    </button>
  );
}

export function TabPanel({ name, children }: { name: string; children: React.ReactNode }) {
  const ctx = useContext(Ctx);
  if (!ctx) throw new Error('TabPanel must be inside Tabs');
  return ctx.active === name ? <div role="tabpanel">{children}</div> : null;
}

Usage:

<Tabs defaultTab="overview">
  <Tab name="overview">Overview</Tab>
  <Tab name="settings">Settings</Tab>
  <TabPanel name="overview"><OverviewContent /></TabPanel>
  <TabPanel name="settings"><SettingsContent /></TabPanel>
</Tabs>

Render props (when children + context don't fit)

type Props<T> = {
  data: T[];
  render: (item: T, index: number) => React.ReactNode;
};

export function List<T>({ data, render }: Props<T>) {
  return <ul>{data.map((item, i) => <li key={i}>{render(item, i)}</li>)}</ul>;
}

forwardRef (React ≤18) / ref-as-prop (React 19+)

// React 19+ — ref is a regular prop
type ButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement> & { ref?: React.Ref<HTMLButtonElement> };
export function Button({ ref, ...rest }: ButtonProps) {
  return <button ref={ref} {...rest} />;
}

// React ≤18 — forwardRef
import { forwardRef } from 'react';
export const Button = forwardRef<HTMLButtonElement, React.ButtonHTMLAttributes<HTMLButtonElement>>(
  function Button(props, ref) {
    return <button ref={ref} {...props} />;
  }
);

Match the React major version's idiom.

Effects: when (and when not)

useEffect is for synchronizing with external systems. NOT for:

// ❌ Don't use effect to derive state
function Bad({ users }: { users: User[] }) {
  const [count, setCount] = useState(0);
  useEffect(() => setCount(users.length), [users]);
  return <div>{count}</div>;
}

// ✅ Compute during render
function Good({ users }: { users: User[] }) {
  return <div>{users.length}</div>;
}
// ❌ Don't use effect for event-driven logic
function Bad() {
  const [clicked, setClicked] = useState(false);
  useEffect(() => {
    if (clicked) reportAnalytics();
  }, [clicked]);
  return <button onClick={() => setClicked(true)}>Click</button>;
}

// ✅ Handle the event directly
function Good() {
  return <button onClick={() => reportAnalytics()}>Click</button>;
}

Effect IS correct for:

  • Subscribing to external sources (WebSocket, EventEmitter, BroadcastChannel).
  • DOM measurement / focus / scroll restoration.
  • Browser API observers (IntersectionObserver, ResizeObserver, MutationObserver) — with cleanup.
  • Manual data fetching when not using TanStack Query / SWR (most projects use those).

Always return a cleanup function for subscriptions:

useEffect(() => {
  const handler = () => doSomething();
  window.addEventListener('resize', handler);
  return () => window.removeEventListener('resize', handler);
}, []);

Performance escape hatches (use sparingly)

React.memo

export const ExpensiveChild = React.memo(function ExpensiveChild({ data }: Props) {
  // ...
});

Wraps a component in shallow-prop comparison. Don't apply by reflex — measure first. Many components don't need it; some have unstable props that defeat memoization anyway.

useMemo / useCallback

const sorted = useMemo(() => expensiveSort(items), [items]);
const onClick = useCallback((id: string) => { /* ... */ }, [dep]);

Only when:

  • The computation is genuinely expensive (profile first).
  • Reference stability matters for downstream memo or effect deps.

List virtualization

For lists > 100 items, use react-window or @tanstack/react-virtual. Renders only visible rows.

Code splitting

import { lazy, Suspense } from 'react';

const HeavyChart = lazy(() => import('./HeavyChart'));

export function Dashboard() {
  return (
    <div>
      <h1>Dashboard</h1>
      <Suspense fallback={<div>Loading chart...</div>}>
        <HeavyChart />
      </Suspense>
    </div>
  );
}

Routes are the natural split boundary.

Accessibility (table stakes)

  • Use semantic HTML: <button> for clickable, <a> for navigation. Don't rebuild buttons from <div onClick>.
  • Form inputs paired with <label> (visible or via aria-label).
  • Modal / dialog: role="dialog", focus trap, ESC to close, focus return on close.
  • Image: alt attribute (empty if decorative).
  • Lists: real <ul>/<ol>/<li> for screen reader semantics.

Anti-patterns

  • ❌ useEffect(() => { setState(...) }, [otherState]) to derive state — compute in render.
  • ❌ Index as key for reorderable lists.
  • ❌ dangerouslySetInnerHTML without sanitization.
  • ❌ <div onClick={...}> for clickable — use <button>.
  • ❌ Stuffing logic into useEffect instead of event handlers.
  • ❌ useCallback / useMemo everywhere "for performance" without profiling.
  • ❌ Storing derived data in state that could be computed.
  • ❌ Disabling react-hooks/exhaustive-deps ESLint rule per-line — fix the deps.
  • ❌ Mounting/unmounting an effect just to invoke it once (use useEffect(() => fn(), []) deliberately).
  • ❌ Reading process.env.X for secrets in components — env vars are public in client bundles.

レビュー

まだレビューはありません。使ってみた感想をお寄せください。

同じリポジトリのスキル

概要と使いどころ

Angular 18-21 project structure, standalone components vs NgModule, control flow (@if/@for/@switch + *ngIf/*ngFor legacy), decorators, dependency injection (inject() function), lifecycle hooks, pipes, Angular Universal SSR pointer. Use this skill to: - Detect project style (standalone vs NgModule) and apply matching patterns. - Pick correct decorators and DI approach. - Use modern control flow (@if/@for/@switch) in Angular 17+ projects. - Apply `inject()` function over constructor injection where appropriate. - Wire bootstrap correctly (bootstrapApplication for standalone, AppModule for legacy). Do NOT use this skill for: - State management (see angular-state-and-rx). - Routing (see angular-routing). - Forms (see angular-forms). - Testing (see angular-testing).

日本語の概要は準備中です。原文の説明を表示しています。

AratKruglik/claude-sdlc362026年9月21日 更新

Angular forms: Reactive Forms (preferred — typed FormGroup/FormControl since Angular 14, FormBuilder, custom + async validators, FormArray, multi-step) and Template-driven (`[(ngModel)]` + FormsModule). Validation strategies, server error mapping, accessibility. Use this skill to: - Build Reactive Forms with typed FormGroup/FormControl. - Use FormBuilder для concise syntax. - Implement custom synchronous and async validators. - Wire FormArray for dynamic field lists. - Map server errors back to form fields. - Pick Reactive vs Template-driven (prefer Reactive). Do NOT use this skill for: - General conventions (see angular-conventions). - State management beyond forms (see angular-state-and-rx). - Routing (see angular-routing). - Testing forms (see angular-testing).

日本語の概要は準備中です。原文の説明を表示しています。

AratKruglik/claude-sdlc362026年9月21日 更新

Angular Router (built-in `@angular/router`) — route configuration for standalone and NgModule projects, functional guards (Angular 14.1+), lazy loading, route resolvers, typed params via signals/observables, programmatic navigation, route data and meta. Use this skill to: - Configure routes (standalone-style or NgModule-style). - Use functional guards (canActivate as function, preferred over class-based in 17+). - Lazy-load components or feature modules. - Implement auth guards via route meta + functional guards. - Read params/queries via `inject(ActivatedRoute)` + signals or RxJS. Do NOT use this skill for: - General conventions (see angular-conventions). - State management (see angular-state-and-rx). - Forms (see angular-forms). - Testing routes (see angular-testing).

日本語の概要は準備中です。原文の説明を表示しています。

AratKruglik/claude-sdlc362026年9月21日 更新

State management for Angular 18-21: signals (signal/computed/effect), services-as-state, NgRx Store + Effects + Selectors, NgRx Component Store, NgRx Signals (newer signal-based store). RxJS essentials — operators, async pipe, takeUntilDestroyed, signal/observable interop. Use this skill to: - Pick the right state tool (signals / services / NgRx variant / vue-query equivalent). - Use signals correctly (signal/computed/effect — when each). - Build a Pinia-style service-as-state singleton. - Set up NgRx Store + Effects + Selectors. - Use RxJS without leaking subscriptions (async pipe, takeUntilDestroyed, Subject patterns). - Bridge signals ↔ observables via toSignal / toObservable. Do NOT use this skill for: - General Angular conventions (see angular-conventions). - Routing state (see angular-routing). - Form state (see angular-forms). - Testing state (see angular-testing).

日本語の概要は準備中です。原文の説明を表示しています。

AratKruglik/claude-sdlc362026年9月21日 更新

Testing Angular 18-21: TestBed, component harnesses (@angular/cdk/testing), Karma+Jasmine (default historical) vs Jest (jest-preset-angular, modern), Angular Testing Library (RTL-style). HttpClient mocking via HttpTestingController. NgRx Effects testing. Cypress / Playwright e2e. Use this skill to: - Detect runner (Karma+Jasmine vs Jest) and configure correctly. - Write component tests with TestBed. - Use component harnesses for Material / custom UI components. - Mock HttpClient via provideHttpClientTesting + HttpTestingController. - Test signal-based inputs with componentRef.setInput(). - Test NgRx Effects with provideMockActions. Do NOT use this skill for: - General Angular conventions (see angular-conventions). - Routing patterns broadly (see angular-routing — covers testing routes briefly). - Form patterns broadly (see angular-forms).

日本語の概要は準備中です。原文の説明を表示しています。

AratKruglik/claude-sdlc362026年9月21日 更新

Shared conventions for every SDLC development-phase architect agent: hard rules, code quality bar, workflow steps (superpowers invocation, spec reading, codebase exploration, verification), and the report/compact-summary contract. Architects load this skill first, then apply their stack-specific instructions on top.

日本語の概要は準備中です。原文の説明を表示しています。

AratKruglik/claude-sdlc362026年9月21日 更新

AratKruglik のスキルをすべて見る

このスキルの問題を報告する