react-patterns
React 18 patterns, hooks best practices, component composition, and optimization techniques. Use when creating hooks, optimizing performance, or following React patterns. Keywords - React, hooks, useState, useEffect, useMemo, useCallback, patterns, composition.
When & Why to Use This Skill
This Claude skill provides a comprehensive framework for modern React 18 development, focusing on high-performance patterns, TypeScript integration, and industry-standard hooks best practices. It streamlines the creation of scalable frontend architectures by providing optimized templates for component composition, state management, and efficient data fetching with React Query v5.
Use Cases
- Generating type-safe functional components and custom hooks following 2025 React best practices.
- Implementing complex form logic and validation using React Hook Form and Zod discriminated unions.
- Optimizing application performance through strategic memoization and advanced component composition patterns.
- Setting up robust data fetching layers with React Query v5, including cache invalidation and mutation workflows.
- Establishing standardized error handling and API integration patterns using Error Boundaries and custom alert components.
| name | react-patterns |
|---|---|
| description | React 18 patterns, hooks best practices, component composition, and optimization techniques. Use when creating hooks, optimizing performance, or following React patterns. Keywords - React, hooks, useState, useEffect, useMemo, useCallback, patterns, composition. |
| compatibility | Designed for React 18.x with TypeScript, React Query v5 |
| version | "2.0.0" |
| author | jpmorgan-payments |
| lastUpdated | "2025-12-24" |
| priority | high |
React Patterns
Component Patterns
Functional Components
import { FC } from "react";
export interface ComponentNameProps {
/** Clear prop description */
title: string;
/** Optional prop with default */
variant?: "default" | "compact";
/** Callback handlers */
onAction?: () => void;
}
export const ComponentName: FC<ComponentNameProps> = ({
title,
variant = "default",
onAction,
}) => {
// Implementation
return <div>{title}</div>;
};
Component Composition
Use props to control visibility and enable composition:
type ComponentProps = {
hideActions?: boolean;
customComponent?: React.ReactNode;
variant?: "default" | "compact";
children?: React.ReactNode;
};
export const Component: FC<ComponentProps> = ({
hideActions = false,
customComponent,
variant = "default",
children,
}) => {
return (
<div>
{children}
{!hideActions && <ActionButtons />}
{customComponent}
</div>
);
};
Hook Patterns (Modern 2025)
Individual Hook Files
// ✅ CORRECT - Individual hook files
// File: hooks/useComponentData.ts
import { useQuery } from "@tanstack/react-query";
export function useComponentData(id: string) {
return useQuery({
queryKey: ["component-data", id],
queryFn: () => fetch(`/api/data/${id}`),
});
}
// File: hooks/useComponentData.test.tsx
import { renderHook, waitFor } from "@testing-library/react";
import { useComponentData } from "./useComponentData";
describe("useComponentData", () => {
test("fetches data", async () => {
const { result } = renderHook(() => useComponentData("123"));
await waitFor(() => expect(result.current.isSuccess).toBe(true));
});
});
// File: hooks/index.ts
export { useComponentData } from "./useComponentData";
export { useComponentForm } from "./useComponentForm";
Custom Hooks
import { useState, useCallback, useEffect } from "react";
interface UseToggleReturn {
isOpen: boolean;
open: () => void;
close: () => void;
toggle: () => void;
}
export function useToggle(initialValue = false): UseToggleReturn {
const [isOpen, setIsOpen] = useState(initialValue);
const open = useCallback(() => setIsOpen(true), []);
const close = useCallback(() => setIsOpen(false), []);
const toggle = useCallback(() => setIsOpen((prev) => !prev), []);
return { isOpen, open, close, toggle };
}
Data Fetching with React Query
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import { getSmbdoGetClientQueryKey } from "@/api/generated/smbdo";
// Query
export function useAccountData(accountId: string) {
return useQuery({
queryKey: ["account", accountId],
queryFn: () => fetch(`/api/accounts/${accountId}`).then((r) => r.json()),
enabled: !!accountId, // Only run if accountId exists
});
}
// Mutation with cache invalidation
export function useUpdateAccount() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (data: AccountData) =>
fetch("/api/accounts", {
method: "POST",
body: JSON.stringify(data),
}),
onSuccess: (response, variables) => {
// Invalidate related queries
queryClient.invalidateQueries({
queryKey: ["account", variables.id],
});
},
});
}
Form Handling
Basic Form Setup
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
const formSchema = z.object({
email: z.string().email("Invalid email"),
name: z.string().min(1, "Name is required"),
});
type FormData = z.infer<typeof formSchema>;
export const FormComponent: FC = () => {
const form = useForm<FormData>({
resolver: zodResolver(formSchema),
mode: "onBlur", // Validates on blur for better UX
reValidateMode: "onBlur",
});
const onSubmit = (data: FormData) => {
// Handle submission
};
return (
<form onSubmit={form.handleSubmit(onSubmit)}>
{/* Form fields */}
</form>
);
};
Discriminated Union Schemas
For conditional validation:
import { z } from "zod";
const baseSchema = z.object({
commonField: z.string(),
});
export const formSchema = z.discriminatedUnion("type", [
z.object({
type: z.literal("INDIVIDUAL"),
firstName: z.string().min(1, "First name is required"),
lastName: z.string().min(1, "Last name is required"),
}).merge(baseSchema),
z.object({
type: z.literal("ORGANIZATION"),
businessName: z.string().min(1, "Business name is required"),
}).merge(baseSchema),
]);
State Management
Local State
import { useState } from "react";
export const Component: FC = () => {
const [count, setCount] = useState(0);
const [user, setUser] = useState<User | null>(null);
// Update state
setCount((prev) => prev + 1);
setUser({ id: 1, name: "John" });
};
Complex State
import { useReducer } from "react";
type State = {
data: Data[];
isLoading: boolean;
error: Error | null;
};
type Action =
| { type: "LOADING" }
| { type: "SUCCESS"; payload: Data[] }
| { type: "ERROR"; error: Error };
function reducer(state: State, action: Action): State {
switch (action.type) {
case "LOADING":
return { ...state, isLoading: true, error: null };
case "SUCCESS":
return { ...state, isLoading: false, data: action.payload };
case "ERROR":
return { ...state, isLoading: false, error: action.error };
default:
return state;
}
}
export const Component: FC = () => {
const [state, dispatch] = useReducer(reducer, {
data: [],
isLoading: false,
error: null,
});
};
Performance Optimization
Memoization
import { useMemo, useCallback } from "react";
export const Component: FC<Props> = ({ data, onAction }) => {
// Memoize expensive computations
const processedData = useMemo(() => {
return data.map((item) => expensiveOperation(item));
}, [data]);
// Memoize callbacks
const handleClick = useCallback(() => {
onAction();
}, [onAction]);
return <div onClick={handleClick}>{processedData}</div>;
};
React.memo
import { memo } from "react";
export const ExpensiveComponent = memo<Props>(({ data }) => {
// Component only re-renders if props change
return <div>{data}</div>;
});
Side Effects
useEffect Best Practices
import { useEffect } from "react";
export const Component: FC = () => {
// Run once on mount
useEffect(() => {
console.log("Component mounted");
// Cleanup on unmount
return () => {
console.log("Component unmounted");
};
}, []);
// Run when dependencies change
useEffect(() => {
if (userId) {
fetchUserData(userId);
}
}, [userId]);
// Avoid: Missing dependencies
useEffect(() => {
// ❌ Missing dependency warning
doSomething(prop);
}, []); // Should include [prop]
};
Error Handling
Error Boundaries
import { Component, ErrorInfo, ReactNode } from "react";
interface Props {
children: ReactNode;
fallback?: ReactNode;
}
interface State {
hasError: boolean;
}
export class ErrorBoundary extends Component<Props, State> {
state = { hasError: false };
static getDerivedStateFromError(): State {
return { hasError: true };
}
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
console.error("Error caught:", error, errorInfo);
}
render() {
if (this.state.hasError) {
return this.props.fallback || <div>Something went wrong</div>;
}
return this.props.children;
}
}
API Error Handling
Always use ServerErrorAlert for API errors:
import { ServerErrorAlert } from "@/core/OnboardingFlow/components/ServerErrorAlert";
<ServerErrorAlert
error={apiError}
customErrorMessage={{
"400": "Please check the information you entered.",
"401": "Please log in and try again.",
"500": "An unexpected error occurred.",
default: "An unexpected error occurred.",
}}
tryAgainAction={() => refetch()}
/>
Context Patterns
import { createContext, useContext, FC, ReactNode } from "react";
interface ThemeContext {
theme: "light" | "dark";
toggleTheme: () => void;
}
const ThemeContext = createContext<ThemeContext | undefined>(undefined);
export const ThemeProvider: FC<{ children: ReactNode }> = ({ children }) => {
const [theme, setTheme] = useState<"light" | "dark">("light");
const toggleTheme = useCallback(() => {
setTheme((prev) => (prev === "light" ? "dark" : "light"));
}, []);
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
};
export function useTheme() {
const context = useContext(ThemeContext);
if (!context) {
throw new Error("useTheme must be used within ThemeProvider");
}
return context;
}
Best Practices
- Use TypeScript - Strict mode, explicit types
- Functional components - Always use FC with explicit props
- Individual hook files - One hook per file with colocated tests
- Memoize appropriately - useMemo for expensive computations, useCallback for callbacks passed to children
- Handle cleanup - Always return cleanup function in useEffect
- Provide types - Export interfaces for all props
- Component composition - Build complex UI from simple components
- Error boundaries - Wrap components that might throw
Anti-Patterns to Avoid
❌ Using class components (use functional components)
❌ Missing dependencies in useEffect
❌ Not memoizing expensive computations
❌ Prop drilling (use context or composition)
❌ Mutating state directly
❌ Using any type
❌ Not handling loading/error states
References
- See
embedded-components/ARCHITECTURE.mdfor file structure - See
.github/copilot-instructions.mdfor complete React patterns