All skills
Skillintermediate
Component Patterns
Modern React component architecture for the application emphasizing type safety, lazy loading, and Suspense boundaries.
Claude Code Knowledge Pack7/10/2026
Overview
Component Patterns
Modern React component architecture for the application emphasizing type safety, lazy loading, and Suspense boundaries.
React.FC Pattern (PREFERRED)
Why React.FC
All components use the React.FC pattern for:
- Explicit type safety for props
- Consistent component signatures
- Clear prop interface documentation
- Better IDE autocomplete
Basic Pattern
interface MyComponentProps {
/** User ID to display */
userId: number;
/** Optional callback when action occurs */
onAction?: () => void;
}
return (
<div>
User: {userId}
</div>
);
};
Key Points:
- Props interface defined separately with JSDoc comments
React.FCprovides type safety- Destructure props in parameters
- Default export at bottom
Lazy Loading Pattern
When to Lazy Load
Lazy load components that are:
- Heavy (DataGrid, charts, rich text editors)
- Route-level components
- Modal/dialog content (not shown initially)
- Below-the-fold content
How to Lazy Load
// Lazy load heavy component
const PostDataGrid = React.lazy(() =>
import('./grids/PostDataGrid')
);
// For named exports
const MyComponent = React.lazy(() =>
import('./MyComponent').then(module => ({
default: module.MyComponent
}))
);
Example from PostTable.tsx:
/**
* Main post table container component
*/
// Lazy load PostDataGrid to optimize bundle size
const PostDataGrid = React.lazy(() => import('./grids/PostDataGrid'));
return (
);
};
Suspense Boundaries
SuspenseLoader Component
Import:
// Or
Usage:
What it does:
- Shows loading indicator while lazy component loads
- Smooth fade-in animation
- Consistent loading experience
- Prevents layout shift
Where to Place Suspense Boundaries
Route Level:
// routes/my-route/index.tsx
const MyPage = lazy(() => import('@/features/my-feature/components/MyPage'));
function Route() {
return (
);
}
Component Level:
function ParentComponent() {
return (
);
}
Multiple Boundaries:
function Page() {
return (
);
}
Each section loads independently, better UX.
Component Structure Template
Recommended Order
/**
* Component description
* What it does, when to use it
*/
// Feature imports
// Component imports
// Hooks
// 1. PROPS INTERFACE (with JSDoc)
interface MyComponentProps {
/** The ID of the entity to display */
entityId: number;
/** Optional callback when action completes */
onComplete?: () => void;
/** Display mode */
mode?: 'view' | 'edit';
}
// 2. STYLES (if inline and <100 lines)
const componentStyles: Record<string, SxProps> = {
container: {
p: 2,
display: 'flex',
flexDirection: 'column',
},
header: {
mb: 2,
display: 'flex',
justifyContent: 'space-between',
},
};
// 3. COMPONENT DEFINITION
entityId,
onComplete,
mode = 'view',
}) => {
// 4. HOOKS (in this order)
// - Context hooks first
const { user } = useAuth();
const { showSuccess, showError } = useMuiSnackbar();
// - Data fetching
const { data } = useSuspenseQuery({
queryKey: ['myEntity', entityId],
queryFn: () => myFeatureApi.getEntity(entityId),
});
// - Local state
const [selectedItem, setSelectedItem] = useState<string | null>(null);
const [isEditing, setIsEditing] = useState(mode === 'edit');
// - Memoized values
const filteredData = useMemo(() => {
return data.filter(item => item.active);
}, [data]);
// - Effects
useEffect(() => {
// Setup
return () => {
// Cleanup
};
}, []);
// 5. EVENT HANDLERS (with useCallback)
const handleItemSelect = useCallback((itemId: string) => {
setSelectedItem(itemId);
}, []);
const handleSave = useCallback(async () => {
try {
await myFeatureApi.updateEntity(entityId, { /* data */ });
showSuccess('Entity updated successfully');
onComplete?.();
} catch (error) {
showError('Failed to update entity');
}
}, [entityId, onComplete, showSuccess, showError]);
// 6. RENDER
return (
<h2>My Component</h2>
Save
{filteredData.map(item => (
<div key={item.id}>{item.name}</div>
))}
);
};
// 7. EXPORT (default export at bottom)
Component Separation
When to Split Components
Split into multiple components when:
- Component exceeds 300 lines
- Multiple distinct responsibilities
- Reusable sections
- Complex nested JSX
Example:
// ❌ AVOID - Monolithic
function MassiveComponent() {
// 500+ lines
// Search logic
// Filter logic
// Grid logic
// Action panel logic
}
// ✅ PREFERRED - Modular
function ParentContainer() {
return (
);
}
When to Keep Together
Keep in same file when:
- Component < 200 lines
- Tightly coupled logic
- Not reusable elsewhere
- Simple presentation component
Export Patterns
Named Const + Default Export (PREFERRED)
// Component logic
};
Why:
- Named export for testing/refactoring
- Default export for lazy loading convenience
- Both options available to consumers
Lazy Loading Named Exports
const MyComponent = React.lazy(() =>
import('./MyComponent').then(module => ({
default: module.MyComponent
}))
);
Component Communication
Props Down, Events Up
// Parent
function Parent() {
const [selectedId, setSelectedId] = useState<string | null>(null);
return (
);
}
// Child
interface ChildProps {
data: Data[];
onSelect: (id: string) => void;
}
return (
<div onClick={() => onSelect(data[0].id)}>
</div>
);
};
Avoid Prop Drilling
Use context for deep nesting:
// ❌ AVOID - Prop drilling 5+ levels
// Finally uses it here
// ✅ PREFERRED - Context or TanStack Query
const MyContext = createContext(null);
function Provider({ children }) {
const { data } = useSuspenseQuery({ ... });
return <MyContext.Provider value={data}>{children}</MyContext.Provider>;
}
function DeepChild() {
const data = useContext(MyContext);
// Use data directly
}
Advanced Patterns
Compound Components
// Card.tsx
Header: typeof CardHeader;
Body: typeof CardBody;
Footer: typeof CardFooter;
} = ({ children }) => {
return {children};
};
Card.Header = CardHeader;
Card.Body = CardBody;
Card.Footer = CardFooter;
// Usage
<Card.Header>Title</Card.Header>
<Card.Body>Content</Card.Body>
<Card.Footer>Actions</Card.Footer>
Render Props (Rare, but useful)
interface DataProviderProps {
children: (data: Data) => React.ReactNode;
}
const { data } = useSuspenseQuery({ ... });
return <>{children(data)}</>;
};
// Usage
{(data) => }
Summary
Modern Component Recipe:
React.FCwith TypeScript- Lazy load if heavy:
React.lazy(() => import()) - Wrap in `` for loading
- Use
useSuspenseQueryfor data - Import aliases (@/, ~types, ~components)
- Event handlers with
useCallback - Default export at bottom
- No early returns for loading states
See Also:
- data-fetching.md - useSuspenseQuery details
- loading-and-error-states.md - Suspense best practices
- complete-examples.md - Full working examples