← スキル一覧に戻る

component-patterns
by IvanTorresEdge
⭐ 0🍴 1📅 2026年1月13日
SKILL.md
name: component-patterns description: React component composition patterns including compound components and render props. Use when designing reusable components.
React Component Patterns Skill
This skill covers component composition patterns for building reusable, flexible components.
When to Use
Use this skill when:
- Building component libraries
- Creating flexible, reusable components
- Designing component APIs
- Implementing complex UI patterns
Core Principle
COMPOSITION OVER CONFIGURATION - Build flexible components through composition rather than prop overload.
Basic Component Pattern
Props Interface
interface ButtonProps {
variant?: 'primary' | 'secondary' | 'danger';
size?: 'sm' | 'md' | 'lg';
disabled?: boolean;
loading?: boolean;
onClick?: () => void;
children: React.ReactNode;
}
export function Button({
variant = 'primary',
size = 'md',
disabled = false,
loading = false,
onClick,
children,
}: ButtonProps): React.ReactElement {
return (
<button
type="button"
className={cn(buttonVariants({ variant, size }))}
disabled={disabled || loading}
onClick={onClick}
>
{loading && <Spinner className="mr-2" />}
{children}
</button>
);
}
Compound Components
Components that work together to form a cohesive unit.
Context-Based Compound Components
import { createContext, useContext, useState } from 'react';
interface TabsContextValue {
activeTab: string;
setActiveTab: (tab: string) => void;
}
const TabsContext = createContext<TabsContextValue | null>(null);
function useTabsContext(): TabsContextValue {
const context = useContext(TabsContext);
if (!context) {
throw new Error('Tabs components must be used within Tabs');
}
return context;
}
// Root Component
interface TabsProps {
defaultValue: string;
children: React.ReactNode;
}
function Tabs({ defaultValue, children }: TabsProps): React.ReactElement {
const [activeTab, setActiveTab] = useState(defaultValue);
return (
<TabsContext.Provider value={{ activeTab, setActiveTab }}>
<div className="tabs">{children}</div>
</TabsContext.Provider>
);
}
// Tab List
function TabList({ children }: { children: React.ReactNode }): React.ReactElement {
return <div className="tab-list" role="tablist">{children}</div>;
}
// Tab Trigger
interface TabTriggerProps {
value: string;
children: React.ReactNode;
}
function TabTrigger({ value, children }: TabTriggerProps): React.ReactElement {
const { activeTab, setActiveTab } = useTabsContext();
return (
<button
type="button"
role="tab"
aria-selected={activeTab === value}
className={cn('tab-trigger', activeTab === value && 'active')}
onClick={() => setActiveTab(value)}
>
{children}
</button>
);
}
// Tab Content
interface TabContentProps {
value: string;
children: React.ReactNode;
}
function TabContent({ value, children }: TabContentProps): React.ReactElement | null {
const { activeTab } = useTabsContext();
if (activeTab !== value) return null;
return (
<div role="tabpanel" className="tab-content">
{children}
</div>
);
}
// Attach sub-components
Tabs.List = TabList;
Tabs.Trigger = TabTrigger;
Tabs.Content = TabContent;
export { Tabs };
// Usage
function App(): React.ReactElement {
return (
<Tabs defaultValue="tab1">
<Tabs.List>
<Tabs.Trigger value="tab1">Tab 1</Tabs.Trigger>
<Tabs.Trigger value="tab2">Tab 2</Tabs.Trigger>
</Tabs.List>
<Tabs.Content value="tab1">Content 1</Tabs.Content>
<Tabs.Content value="tab2">Content 2</Tabs.Content>
</Tabs>
);
}
Render Props Pattern
Pass a function as children or prop to control rendering.
interface MousePosition {
x: number;
y: number;
}
interface MouseTrackerProps {
children: (position: MousePosition) => React.ReactElement;
}
function MouseTracker({ children }: MouseTrackerProps): React.ReactElement {
const [position, setPosition] = useState<MousePosition>({ x: 0, y: 0 });
useEffect(() => {
const handleMouseMove = (e: MouseEvent): void => {
setPosition({ x: e.clientX, y: e.clientY });
};
window.addEventListener('mousemove', handleMouseMove);
return () => window.removeEventListener('mousemove', handleMouseMove);
}, []);
return children(position);
}
// Usage
function App(): React.ReactElement {
return (
<MouseTracker>
{({ x, y }) => (
<div>
Mouse position: {x}, {y}
</div>
)}
</MouseTracker>
);
}
Slot Pattern
Allow users to replace parts of a component.
interface CardProps {
header?: React.ReactNode;
footer?: React.ReactNode;
children: React.ReactNode;
}
function Card({ header, footer, children }: CardProps): React.ReactElement {
return (
<div className="card">
{header && <div className="card-header">{header}</div>}
<div className="card-body">{children}</div>
{footer && <div className="card-footer">{footer}</div>}
</div>
);
}
// Usage
function App(): React.ReactElement {
return (
<Card
header={<h2>Card Title</h2>}
footer={<Button>Action</Button>}
>
Card content goes here
</Card>
);
}
Polymorphic Components
Components that can render as different elements.
type PolymorphicProps<E extends React.ElementType> = {
as?: E;
} & React.ComponentPropsWithoutRef<E>;
function Box<E extends React.ElementType = 'div'>({
as,
children,
...props
}: PolymorphicProps<E>): React.ReactElement {
const Component = as ?? 'div';
return <Component {...props}>{children}</Component>;
}
// Usage
function App(): React.ReactElement {
return (
<>
<Box>Default div</Box>
<Box as="span">As span</Box>
<Box as="a" href="/about">As link</Box>
<Box as="button" onClick={() => {}}>As button</Box>
</>
);
}
Controlled vs Uncontrolled
Uncontrolled Component
interface UncontrolledInputProps {
defaultValue?: string;
onChange?: (value: string) => void;
}
function UncontrolledInput({ defaultValue, onChange }: UncontrolledInputProps): React.ReactElement {
const [value, setValue] = useState(defaultValue ?? '');
const handleChange = (e: ChangeEvent<HTMLInputElement>): void => {
setValue(e.target.value);
onChange?.(e.target.value);
};
return <input value={value} onChange={handleChange} />;
}
Controlled Component
interface ControlledInputProps {
value: string;
onChange: (value: string) => void;
}
function ControlledInput({ value, onChange }: ControlledInputProps): React.ReactElement {
return (
<input
value={value}
onChange={(e) => onChange(e.target.value)}
/>
);
}
Controlled + Uncontrolled (Flexible)
interface FlexibleInputProps {
value?: string;
defaultValue?: string;
onChange?: (value: string) => void;
}
function FlexibleInput({
value: controlledValue,
defaultValue,
onChange,
}: FlexibleInputProps): React.ReactElement {
const [internalValue, setInternalValue] = useState(defaultValue ?? '');
const isControlled = controlledValue !== undefined;
const value = isControlled ? controlledValue : internalValue;
const handleChange = (e: ChangeEvent<HTMLInputElement>): void => {
if (!isControlled) {
setInternalValue(e.target.value);
}
onChange?.(e.target.value);
};
return <input value={value} onChange={handleChange} />;
}
forwardRef Pattern
Forward refs to inner elements.
import { forwardRef } from 'react';
interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
label: string;
error?: string;
}
const Input = forwardRef<HTMLInputElement, InputProps>(
({ label, error, ...props }, ref) => {
return (
<div className="input-wrapper">
<label>{label}</label>
<input ref={ref} {...props} />
{error && <span className="error">{error}</span>}
</div>
);
}
);
Input.displayName = 'Input';
export { Input };
Best Practices
- Prefer composition - Use children and slots over many props
- Use TypeScript - Strict types for all props
- Provide defaults - Sensible default values
- Forward refs - For focusable/interactive elements
- Display name - Set for DevTools debugging
- Keep components small - Single responsibility
- Colocate related components - Compound components together
Pattern Selection Guide
| Pattern | Use When |
|---|---|
| Props | Simple, few variations |
| Compound | Related components working together |
| Render Props | Need to expose internal state |
| Slots | Customizable sections |
| Polymorphic | Flexible element type |
| Controlled | Parent manages state |
| Uncontrolled | Self-contained state |
Notes
- Start simple, add patterns as needed
- Compound components are great for component libraries
- Use Radix UI primitives for accessible base components
スコア
総合スコア
60/100
リポジトリの品質指標に基づく評価
✓SKILL.md
SKILL.mdファイルが含まれている
+20
✓LICENSE
ライセンスが設定されている
+10
○説明文
100文字以上の説明がある
0/10
○人気
GitHub Stars 100以上
0/15
○最近の活動
3ヶ月以内に更新がある
0/10
○フォーク
10回以上フォークされている
0/5
✓Issue管理
オープンIssueが50未満
+5
✓言語
プログラミング言語が設定されている
+5
○タグ
1つ以上のタグが設定されている
0/5
レビュー
💬
レビュー機能は近日公開予定です