react-patterns
SKILL.md
React Patterns
Modern React 19+ patterns and best practices.
Component Patterns
Composition Over Prop Drilling
// Compose with children instead of passing many props
<Card>
<Card.Header>
<Card.Title>User Profile</Card.Title>
</Card.Header>
<Card.Body>
<UserInfo user={user} />
</Card.Body>
<Card.Footer>
<Button onClick={save}>Save</Button>
</Card.Footer>
</Card>
Render Props for Flexibility
interface ListProps<T> {
items: T[];
renderItem: (item: T, index: number) => ReactNode;
keyExtractor: (item: T) => string;
}
function List<T>({ items, renderItem, keyExtractor }: ListProps<T>) {
return (
<ul>
{items.map((item, i) => (
<li key={keyExtractor(item)}>{renderItem(item, i)}</li>
))}
</ul>
);
}
Hook Patterns
Custom Data Fetching Hook
function useQuery<T>(key: string, fetcher: () => Promise<T>) {
const [state, setState] = useState<{
data: T | null;
error: Error | null;
isLoading: boolean;
}>({ data: null, error: null, isLoading: true });
useEffect(() => {
let cancelled = false;
setState(prev => ({ ...prev, isLoading: true }));
fetcher()
.then(data => { if (!cancelled) setState({ data, error: null, isLoading: false }); })
.catch(error => { if (!cancelled) setState({ data: null, error, isLoading: false }); });
return () => { cancelled = true; };
}, [key]);
return state;
}
useCallback for Stable References
const handleSubmit = useCallback(
async (data: FormData) => {
await api.submit(userId, data);
onSuccess();
},
[userId, onSuccess]
);
useMemo for Expensive Computations
const sortedItems = useMemo(
() => [...items].sort((a, b) => a.name.localeCompare(b.name)),
[items]
);
State Management
Local State First
// Start simple
const [isOpen, setIsOpen] = useState(false);
// Graduate to useReducer for complex state
const [state, dispatch] = useReducer(cartReducer, initialCart);
// Context for cross-component state
const ThemeContext = createContext<Theme>('light');
Server State (React Query / SWR)
const { data, isLoading } = useQuery({
queryKey: ['users', userId],
queryFn: () => fetchUser(userId),
staleTime: 5 * 60 * 1000,
});
Performance
Code Splitting
const Dashboard = lazy(() => import('./Dashboard'));
function App() {
return (
<Suspense fallback={<Skeleton />}>
<Dashboard />
</Suspense>
);
}
Virtualization for Long Lists
import { useVirtualizer } from '@tanstack/react-virtual';
function VirtualList({ items }: { items: Item[] }) {
const parentRef = useRef<HTMLDivElement>(null);
const virtualizer = useVirtualizer({
count: items.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 50,
});
return (
<div ref={parentRef} style={{ overflow: 'auto', height: 400 }}>
{virtualizer.getVirtualItems().map(row => (
<div key={row.key} style={{ transform: `translateY(${row.start}px)` }}>
{items[row.index].name}
</div>
))}
</div>
);
}
Error Boundaries
class ErrorBoundary extends Component<Props, { hasError: boolean }> {
state = { hasError: false };
static getDerivedStateFromError() {
return { hasError: true };
}
render() {
if (this.state.hasError) return this.props.fallback;
return this.props.children;
}
}
Checklist
- Functional components only (no class components for new code)
- Custom hooks for reusable stateful logic
- Correct dependency arrays in useEffect/useCallback/useMemo
- Error boundaries at route level
- Loading and error states handled for async data
- Accessibility: ARIA labels, keyboard navigation, focus management
- Code splitting for route-level components
- Keys use stable, unique identifiers (not array index)
Weekly Installs
2
Repository
peopleforrester…dotfilesGitHub Stars
1
First Seen
13 days ago
Security Audits
Installed on
opencode2
gemini-cli2
codebuddy2
github-copilot2
codex2
kimi-cli2