원클릭으로
react-patterns
Modern React 19+ patterns. Activated when working with state management, Suspense, Compound Components, or React hooks.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
Modern React 19+ patterns. Activated when working with state management, Suspense, Compound Components, or React hooks.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
AGENTS.md authoring patterns. Activated when writing AI agent context files, working with agents.md or AGENTS.md.
Codebase onboarding guide. Activated when starting a new project analysis, requesting systematic codebase understanding, or running /onboard.
Feature-Sliced Design architecture patterns. Activated when working with FSD layers, slices, dependency rules, or structuring frontend code.
React Query server state management patterns. Activated when applying Query Factory, queryOptions, or mutationOptions patterns.
Practical TypeScript patterns. Activated when working with type inference, utility types, generics, type guards, or type narrowing.
Writing readable, maintainable code. Activated when applying naming conventions, function decomposition, or Early Return patterns.
SOC 직업 분류 기준
| name | react-patterns |
| description | Modern React 19+ patterns. Activated when working with state management, Suspense, Compound Components, or React hooks. |
React 19+ era patterns
function UserCard({ user }: { user: User }) {
return (
<div className="user-card">
<img src={user.avatar} alt="" />
<span>{user.name}</span>
</div>
);
}
When it grows, split into same-folder modules:
// features/user/
// ├── user-card.tsx
// ├── user-avatar.tsx
// ├── user-info.tsx
// └── index.ts
Who needs this state?
├─ This component only → useState
├─ Parent-child few levels → props
├─ Distant multiple components → Context or state library
└─ Server data → React Query
// Simple state → useState
const [isOpen, setIsOpen] = useState(false);
// Complex/related state → useReducer
const [state, dispatch] = useReducer(formReducer, initialState);
<Select value={selected} onChange={setSelected}>
<Select.Trigger>Choose</Select.Trigger>
<Select.Content>
<Select.Item value="a">Option A</Select.Item>
<Select.Item value="b">Option B</Select.Item>
</Select.Content>
</Select>
// Implementation
const SelectContext = createContext<SelectContextValue | null>(null);
function Select({ value, onChange, children }) {
return (
<SelectContext.Provider value={{ value, onChange }}>
{children}
</SelectContext.Provider>
);
}
Select.Trigger = function Trigger({ children }) { ... };
Select.Content = function Content({ children }) { ... };
Select.Item = function Item({ value, children }) { ... };
| Pattern | Use When |
|---|---|
| Custom Hook | Logic reuse (most cases) |
| Render Props | Library API design, UI customization needed |
// Custom Hook (preferred)
function useMousePosition() {
const [position, setPosition] = useState({ x: 0, y: 0 });
// ...
return position;
}
class ErrorBoundary extends React.Component<Props, State> {
state = { hasError: false, error: null };
static getDerivedStateFromError(error: Error) {
return { hasError: true, error };
}
render() {
if (this.state.hasError) {
return this.props.fallback ?? <DefaultError error={this.state.error} />;
}
return this.props.children;
}
}
// Recommend: react-error-boundary library
<ErrorBoundary fallback={<ErrorPage />}>
<App />
</ErrorBoundary>
// Data loading
<Suspense fallback={<Skeleton />}>
<UserProfile /> {/* uses useSuspenseQuery */}
</Suspense>
// Code splitting
const AdminPanel = lazy(() => import('./admin-panel'));
<Suspense fallback={<Loading />}>
<AdminPanel />
</Suspense>
// Nested Suspense for progressive loading
<Suspense fallback={<PageSkeleton />}>
<Header />
<Suspense fallback={<ContentSkeleton />}>
<Content />
</Suspense>
<Suspense fallback={<SidebarSkeleton />}>
<Sidebar />
</Suspense>
</Suspense>
// Read Promise directly (requires Suspense)
function UserProfile({ userPromise }: { userPromise: Promise<User> }) {
const user = use(userPromise);
return <div>{user.name}</div>;
}
// Read Context (replaces useContext)
function Button() {
const theme = use(ThemeContext);
return <button className={theme.button}>Click</button>;
}
async function submitForm(prevState: State, formData: FormData) {
const result = await api.post('/submit', Object.fromEntries(formData));
return { success: true, data: result };
}
function Form() {
const [state, formAction, isPending] = useActionState(submitForm, null);
return (
<form action={formAction}>
<input name="email" />
<button disabled={isPending}>
{isPending ? 'Submitting...' : 'Submit'}
</button>
{state?.success && <p>Done!</p>}
</form>
);
}
function LikeButton({ likes, onLike }: Props) {
const [optimisticLikes, addOptimistic] = useOptimistic(
likes,
(current, added: number) => current + added
);
const handleClick = async () => {
addOptimistic(1);
await onLike();
};
return <button onClick={handleClick}>{optimisticLikes}</button>;
}
// AVOID: useEffect for state sync
const [filteredItems, setFilteredItems] = useState([]);
useEffect(() => {
setFilteredItems(items.filter(i => i.active));
}, [items]);
// USE: compute during render
const filteredItems = useMemo(() => items.filter(i => i.active), [items]);
// AVOID: useEffect for data fetching
useEffect(() => { fetchUser().then(setUser); }, []);
// USE: React Query
const { data: user } = useQuery({ queryKey: ['user'], queryFn: fetchUser });
// AVOID: forwardRef (React 19 passes ref as prop)
const Input = forwardRef((props, ref) => <input ref={ref} {...props} />);
// USE: React 19+
function Input({ ref, ...props }) {
return <input ref={ref} {...props} />;
}
| Principle | Description |
|---|---|
| Start simple | Choose the simplest solution that solves the problem |
| Derive, don't sync | Compute values instead of syncing with useEffect |
| Hooks for logic | Extract logic into custom hooks, keep components UI-only |