[ PROMPT_NODE_24793 ]
Complete Examples
[ SKILL_DOCUMENTATION ]
# Complete Examples
Full working examples combining all modern patterns: React.FC, lazy loading, Suspense, useSuspenseQuery, styling, routing, and error handling.
---
## Example 1: Complete Modern Component
Combines: React.FC, useSuspenseQuery, cache-first, useCallback, styling, error handling
```typescript
/**
* User profile display component
* Demonstrates modern patterns with Suspense and TanStack Query
*/
import React, { useState, useCallback, useMemo } from 'react';
import { Box, Paper, Typography, Button, Avatar } from '@mui/material';
import type { SxProps, Theme } from '@mui/material';
import { useSuspenseQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { userApi } from '../api/userApi';
import { useMuiSnackbar } from '@/hooks/useMuiSnackbar';
import type { User } from '~types/user';
// Styles object
const componentStyles: Record<string, SxProps> = {
container: {
p: 3,
maxWidth: 600,
margin: '0 auto',
},
header: {
display: 'flex',
alignItems: 'center',
gap: 2,
mb: 3,
},
content: {
display: 'flex',
flexDirection: 'column',
gap: 2,
},
actions: {
display: 'flex',
gap: 1,
mt: 2,
},
};
interface UserProfileProps {
userId: string;
onUpdate?: () => void;
}
export const UserProfile: React.FC = ({ userId, onUpdate }) => {
const queryClient = useQueryClient();
const { showSuccess, showError } = useMuiSnackbar();
const [isEditing, setIsEditing] = useState(false);
// Suspense query - no isLoading needed!
const { data: user } = useSuspenseQuery({
queryKey: ['user', userId],
queryFn: () => userApi.getUser(userId),
staleTime: 5 * 60 * 1000,
});
// Update mutation
const updateMutation = useMutation({
mutationFn: (updates: Partial) =>
userApi.updateUser(userId, updates),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['user', userId] });
showSuccess('Profile updated');
setIsEditing(false);
onUpdate?.();
},
onError: () => {
showError('Failed to update profile');
},
});
// Memoized computed value
const fullName = useMemo(() => {
return `${user.firstName} ${user.lastName}`;
}, [user.firstName, user.lastName]);
// Event handlers with useCallback
const handleEdit = useCallback(() => {
setIsEditing(true);
}, []);
const handleSave = useCallback(() => {
updateMutation.mutate({
firstName: user.firstName,
lastName: user.lastName,
});
}, [user, updateMutation]);
const handleCancel = useCallback(() => {
setIsEditing(false);
}, []);
return (
{user.firstName[0]}{user.lastName[0]}
{fullName}
{user.email}
Username: {user.username}
Roles: {user.roles.join(', ')}
{!isEditing ? (
) : (
>
)}
</Box>
);
};
export default UserProfile;
```
**Usage:**
```typescript
console.log('Updated')} />
```
---
## Example 2: Complete Feature Structure
Real example based on `features/posts/`:
```
features/
users/
api/
userApi.ts # API service layer
components/
UserProfile.tsx # Main component (from Example 1)
UserList.tsx # List component
UserBlog.tsx # Blog component
modals/
DeleteUserModal.tsx # Modal component
hooks/
useSuspenseUser.ts # Suspense query hook
useUserMutations.ts # Mutation hooks
useUserPermissions.ts # Feature-specific hook
helpers/
userHelpers.ts # Utility functions
validation.ts # Validation logic
types/
index.ts # TypeScript interfaces
index.ts # Public API exports
```
### API Service (userApi.ts)
```typescript
import apiClient from '@/lib/apiClient';
import type { User, CreateUserPayload, UpdateUserPayload } from '../types';
export const userApi = {
getUser: async (userId: string): Promise => {
const { data } = await apiClient.get(`/users/${userId}`);
return data;
},
getUsers: async (): Promise => {
const { data } = await apiClient.get('/users');
return data;
},
createUser: async (payload: CreateUserPayload): Promise => {
const { data } = await apiClient.post('/users', payload);
return data;
},
updateUser: async (userId: string, payload: UpdateUserPayload): Promise => {
const { data } = await apiClient.put(`/users/${userId}`, payload);
return data;
},
deleteUser: async (userId: string): Promise => {
await apiClient.delete(`/users/${userId}`);
},
};
```
### Suspense Hook (useSuspenseUser.ts)
```typescript
import { useSuspenseQuery } from '@tanstack/react-query';
import { userApi } from '../api/userApi';
import type { User } from '../types';
export function useSuspenseUser(userId: string) {
return useSuspenseQuery({
queryKey: ['user', userId],
queryFn: () => userApi.getUser(userId),
staleTime: 5 * 60 * 1000,
gcTime: 10 * 60 * 1000,
});
}
export function useSuspenseUsers() {
return useSuspenseQuery({
queryKey: ['users'],
queryFn: () => userApi.getUsers(),
staleTime: 1 * 60 * 1000, // Shorter for list
});
}
```
### Types (types/index.ts)
```typescript
export interface User {
id: string;
username: string;
email: string;
firstName: string;
lastName: string;
roles: string[];
createdAt: string;
updatedAt: string;
}
export interface CreateUserPayload {
username: string;
email: string;
firstName: string;
lastName: string;
password: string;
}
export type UpdateUserPayload = Partial<Omit>;
```
### Public Exports (index.ts)
```typescript
// Export components
export { UserProfile } from './components/UserProfile';
export { UserList } from './components/UserList';
// Export hooks
export { useSuspenseUser, useSuspenseUsers } from './hooks/useSuspenseUser';
export { useUserMutations } from './hooks/useUserMutations';
// Export API
export { userApi } from './api/userApi';
// Export types
export type { User, CreateUserPayload, UpdateUserPayload } from './types';
```
---
## Example 3: Complete Route with Lazy Loading
```typescript
/**
* User profile route
* Path: /users/:userId
*/
import { createFileRoute } from '@tanstack/react-router';
import { lazy } from 'react';
import { SuspenseLoader } from '~components/SuspenseLoader';
// Lazy load the UserProfile component
const UserProfile = lazy(() =>
import('@/features/users/components/UserProfile').then(
(module) => ({ default: module.UserProfile })
)
);
export const Route = createFileRoute('/users/$userId')({
component: UserProfilePage,
loader: ({ params }) => ({
crumb: `User ${params.userId}`,
}),
});
function UserProfilePage() {
const { userId } = Route.useParams();
return (
console.log('Profile updated')}
/>
);
}
export default UserProfilePage;
```
---
## Example 4: List with Search and Filtering
```typescript
import React, { useState, useMemo } from 'react';
import { Box, TextField, List, ListItem } from '@mui/material';
import { useDebounce } from 'use-debounce';
import { useSuspenseQuery } from '@tanstack/react-query';
import { userApi } from '../api/userApi';
export const UserList: React.FC = () => {
const [searchTerm, setSearchTerm] = useState('');
const [debouncedSearch] = useDebounce(searchTerm, 300);
const { data: users } = useSuspenseQuery({
queryKey: ['users'],
queryFn: () => userApi.getUsers(),
});
// Memoized filtering
const filteredUsers = useMemo(() => {
if (!debouncedSearch) return users;
return users.filter(user =>
user.name.toLowerCase().includes(debouncedSearch.toLowerCase()) ||
user.email.toLowerCase().includes(debouncedSearch.toLowerCase())
);
}, [users, debouncedSearch]);
return (
setSearchTerm(e.target.value)}
placeholder='Search users...'
fullWidth
sx={{ mb: 2 }}
/>
{filteredUsers.map(user => (
{user.name} - {user.email}
))}
);
};
```
---
## Example 5: Blog with Validation
```typescript
import React from 'react';
import { Box, TextField, Button, Paper } from '@mui/material';
import { useBlog } from 'react-hook-blog';
import { zodResolver } from '@hookblog/resolvers/zod';
import { z } from 'zod';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { userApi } from '../api/userApi';
import { useMuiSnackbar } from '@/hooks/useMuiSnackbar';
const userSchema = z.object({
username: z.string().min(3).max(50),
email: z.string().email(),
firstName: z.string().min(1),
lastName: z.string().min(1),
});
type UserBlogData = z.infer;
interface CreateUserBlogProps {
onSuccess?: () => void;
}
export const CreateUserBlog: React.FC = ({ onSuccess }) => {
const queryClient = useQueryClient();
const { showSuccess, showError } = useMuiSnackbar();
const { register, handleSubmit, blogState: { errors }, reset } = useBlog({
resolver: zodResolver(userSchema),
defaultValues: {
username: '',
email: '',
firstName: '',
lastName: '',
},
});
const createMutation = useMutation({
mutationFn: (data: UserBlogData) => userApi.createUser(data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['users'] });
showSuccess('User created successfully');
reset();
onSuccess?.();
},
onError: () => {
showError('Failed to create user');
},
});
const onSubmit = (data: UserBlogData) => {
createMutation.mutate(data);
};
return (
);
};
export default CreateUserBlog;
```
---
## Example 2: Parent Container with Lazy Loading
```typescript
import React from 'react';
import { Box } from '@mui/material';
import { SuspenseLoader } from '~components/SuspenseLoader';
// Lazy load heavy components
const UserList = React.lazy(() => import('./UserList'));
const UserStats = React.lazy(() => import('./UserStats'));
const ActivityFeed = React.lazy(() => import('./ActivityFeed'));
export const UserDashboard: React.FC = () => {
return (
);
};
export default UserDashboard;
```
**Benefits:**
- Each section loads independently
- User sees partial content sooner
- Better perceived perblogance
---
## Example 3: Cache-First Strategy Implementation
Complete example based on useSuspensePost.ts:
```typescript
import { useSuspenseQuery, useQueryClient } from '@tanstack/react-query';
import { postApi } from '../api/postApi';
import type { Post } from '../types';
/**
* Smart post hook with cache-first strategy
* Reuses data from grid cache when available
*/
export function useSuspensePost(blogId: number, postId: number) {
const queryClient = useQueryClient();
return useSuspenseQuery({
queryKey: ['post', blogId, postId],
queryFn: async () => {
// Strategy 1: Check grid cache first (avoids API call)
const gridCache = queryClient.getQueryData([
'posts-v2',
blogId,
'summary'
]) || queryClient.getQueryData([
'posts-v2',
blogId,
'flat'
]);
if (gridCache?.rows) {
const cached = gridCache.rows.find(
(row) => row.S_ID === postId
);
if (cached) {
return cached; // Return from cache - no API call!
}
}
// Strategy 2: Not in cache, fetch from API
return postApi.getPost(blogId, postId);
},
staleTime: 5 * 60 * 1000, // Fresh for 5 minutes
gcTime: 10 * 60 * 1000, // Cache for 10 minutes
refetchOnWindowFocus: false, // Don't refetch on focus
});
}
```
**Why this pattern:**
- Checks grid cache before API
- Instant data if user came from grid
- Falls back to API if not cached
- Configurable cache times
---
## Example 4: Complete Route File
```typescript
/**
* Project catalog route
* Path: /project-catalog
*/
import { createFileRoute } from '@tanstack/react-router';
import { lazy } from 'react';
// Lazy load the PostTable component
const PostTable = lazy(() =>
import('@/features/posts/components/PostTable').then(
(module) => ({ default: module.PostTable })
)
);
// Route constants
const PROJECT_CATALOG_FORM_ID = 744;
const PROJECT_CATALOG_PROJECT_ID = 225;
export const Route = createFileRoute('/project-catalog/')({
component: ProjectCatalogPage,
loader: () => ({
crumb: 'Projects', // Breadcrumb title
}),
});
function ProjectCatalogPage() {
return (
);
}
export default ProjectCatalogPage;
```
---
## Example 5: Dialog with Blog
```typescript
import React from 'react';
import {
Dialog,
DialogTitle,
DialogContent,
DialogActions,
Button,
TextField,
Box,
IconButton,
} from '@mui/material';
import { Close, PersonAdd } from '@mui/icons-material';
import { useBlog } from 'react-hook-blog';
import { zodResolver } from '@hookblog/resolvers/zod';
import { z } from 'zod';
const blogSchema = z.object({
name: z.string().min(1),
email: z.string().email(),
});
type BlogData = z.infer;
interface AddUserDialogProps {
open: boolean;
onClose: () => void;
onSubmit: (data: BlogData) => Promise;
}
export const AddUserDialog: React.FC = ({
open,
onClose,
onSubmit,
}) => {
const { register, handleSubmit, blogState: { errors }, reset } = useBlog({
resolver: zodResolver(blogSchema),
});
const handleClose = () => {
reset();
onClose();
};
const handleBlogSubmit = async (data: BlogData) => {
await onSubmit(data);
handleClose();
};
return (
);
};
```
---
## Example 6: Parallel Data Fetching
```typescript
import React from 'react';
import { Box, Grid, Paper } from '@mui/material';
import { useSuspenseQueries } from '@tanstack/react-query';
import { userApi } from '../api/userApi';
import { statsApi } from '../api/statsApi';
import { activityApi } from '../api/activityApi';
export const Dashboard: React.FC = () => {
// Fetch all data in parallel with Suspense
const [statsQuery, usersQuery, activityQuery] = useSuspenseQueries({
queries: [
{
queryKey: ['stats'],
queryFn: () => statsApi.getStats(),
},
{
queryKey: ['users', 'active'],
queryFn: () => userApi.getActiveUsers(),
},
{
queryKey: ['activity', 'recent'],
queryFn: () => activityApi.getRecent(),
},
],
});
return (
Stats
Total: {statsQuery.data.total}
Active Users
Count: {usersQuery.data.length}
Recent Activity
Events: {activityQuery.data.length}
); }; // Usage with Suspense ``` --- ## Example 7: Optimistic Update ```typescript import { useMutation, useQueryClient } from '@tanstack/react-query'; import type { User } from '../types'; export const useToggleUserStatus = () => { const queryClient = useQueryClient(); return useMutation({ mutationFn: (userId: string) => userApi.toggleStatus(userId), // Optimistic update onMutate: async (userId) => { // Cancel outgoing refetches await queryClient.cancelQueries({ queryKey: ['users'] }); // Snapshot previous value const previousUsers = queryClient.getQueryData(['users']); // Optimistically update UI queryClient.setQueryData(['users'], (old) => { return old?.map(user => user.id === userId ? { ...user, active: !user.active } : user ) || []; }); return { previousUsers }; }, // Rollback on error onError: (err, userId, context) => { queryClient.setQueryData(['users'], context?.previousUsers); }, // Refetch after mutation onSettled: () => { queryClient.invalidateQueries({ queryKey: ['users'] }); }, }); }; ``` --- ## Summary **Key Takeaways:** 1. **Component Pattern**: React.FC + lazy + Suspense + useSuspenseQuery 2. **Feature Structure**: Organized subdirectories (api/, components/, hooks/, etc.) 3. **Routing**: Folder-based with lazy loading 4. **Data Fetching**: useSuspenseQuery with cache-first strategy 5. **Blogs**: React Hook Blog + Zod validation 6. **Error Handling**: useMuiSnackbar + onError callbacks 7. **Perblogance**: useMemo, useCallback, React.memo, debouncing 8. **Styling**: Inline <100 lines, sx prop, MUI v7 syntax **See other resources for detailed explanations of each pattern.**
Source: claude-code-templates (MIT). See About Us for full credits.