🔗 Modern Data Fetching Evolution
📈 From Callbacks to Modern Hooks
Callback Hell
// The dark ages of nested callbacks
fetchUser(userId, (user) => {
fetchPosts(user.id, (posts) => {
fetchComments(posts[0].id, (comments) => {
// Finally render something... 😵💫
render(user, posts, comments);
});
});
});
Promise Chains
// Better, but still verbose
fetchUser(userId)
.then(user => fetchPosts(user.id))
.then(posts => fetchComments(posts[0].id))
.then(comments => render(comments))
.catch(error => handleError(error));
Async/Await
// Much cleaner syntax
async function loadUserData(userId) {
try {
const user = await fetchUser(userId);
const posts = await fetchPosts(user.id);
const comments = await fetchComments(posts[0].id);
render(user, posts, comments);
} catch (error) {
handleError(error);
}
}
Modern Hooks
// Modern data fetching with React Query
function UserProfile({ userId }) {
const { data: user, isLoading, error } = useQuery(
['user', userId],
() => fetchUser(userId),
{ staleTime: 5 * 60 * 1000 } // Cache for 5 minutes
);
if (isLoading) return <Loading />;
if (error) return <Error error={error} />;
return <UserCard user={user} />;
}
🚀 Modern Data Fetching Benefits
Smart Caching
Automatic cache management with stale-while-revalidate patterns
Background Updates
Refetch data in background to keep UI responsive
Optimistic Updates
Update UI immediately, rollback on error
Real-time Sync
WebSocket integration and real-time updates
🎣 React Query (TanStack Query) Deep Dive
🎯 What is React Query?
React Query is a powerful data synchronization library that makes fetching, caching, synchronizing and updating server state in React applications a breeze.
🎮 Interactive React Query Demo
🔍 Query States
💾 Cache Status
📊 Fetched Data
⏱️ Request Timeline
🔄 Common React Query Patterns
📋 Basic Query
function Posts() {
const {
data: posts,
isLoading,
error
} = useQuery('posts', fetchPosts);
if (isLoading) return <Loading />;
if (error) return <Error />;
return (
<div>
{posts.map(post =>
<PostCard key={post.id} post={post} />
)}
</div>
);
}
🔄 Mutations
function CreatePost() {
const queryClient = useQueryClient();
const mutation = useMutation(createPost, {
onSuccess: () => {
// Invalidate and refetch posts
queryClient.invalidateQueries('posts');
},
onError: (error) => {
toast.error('Failed to create post');
}
});
return (
<button
onClick={() => mutation.mutate(newPost)}
disabled={mutation.isLoading}
>
{mutation.isLoading ? 'Creating...' : 'Create Post'}
</button>
);
}
⚡ Optimistic Updates
const mutation = useMutation(updatePost, {
onMutate: async (newPost) => {
// Cancel outgoing refetches
await queryClient.cancelQueries(['post', newPost.id]);
// Snapshot previous value
const previousPost = queryClient.getQueryData(['post', newPost.id]);
// Optimistically update
queryClient.setQueryData(['post', newPost.id], newPost);
return { previousPost };
},
onError: (err, newPost, context) => {
// Rollback on error
queryClient.setQueryData(
['post', newPost.id],
context.previousPost
);
},
onSettled: () => {
queryClient.invalidateQueries(['post']);
},
});
🔄 Real-time Updates
function useRealtimePosts() {
const queryClient = useQueryClient();
useEffect(() => {
const socket = io();
socket.on('post-created', (newPost) => {
queryClient.setQueryData('posts', (oldPosts) => [
newPost,
...oldPosts
]);
});
socket.on('post-updated', (updatedPost) => {
queryClient.setQueryData(['post', updatedPost.id], updatedPost);
queryClient.invalidateQueries('posts');
});
return () => socket.disconnect();
}, [queryClient]);
return useQuery('posts', fetchPosts);
}
⚖️ SWR vs React Query: Feature Comparison
SWR
Stale-While-Revalidate data fetching library by Vercel
React Query
Powerful data synchronization for React by TanStack
🔍 Feature-by-Feature Comparison
🎮 Side-by-Side Demo
📡 SWR Implementation
import useSWR from 'swr';
function Profile() {
const { data, error, isLoading } = useSWR(
'/api/user',
fetcher,
{
refreshInterval: 1000,
revalidateOnFocus: true
}
);
if (error) return <div>Failed to load</div>;
if (isLoading) return <div>Loading...</div>;
return <div>Hello {data.name}!</div>;
}
🎣 React Query Implementation
import { useQuery } from '@tanstack/react-query';
function Profile() {
const { data, error, isLoading, isFetching } = useQuery({
queryKey: ['user'],
queryFn: () => fetch('/api/user').then(res => res.json()),
staleTime: 5 * 60 * 1000,
refetchOnWindowFocus: true
});
if (error) return <div>Failed to load</div>;
if (isLoading) return <div>Loading...</div>;
return (
<div>
Hello {data.name}!
{isFetching && <span>Updating...</span>}
</div>
);
}
📊 Performance Comparison
🚀 GraphQL Integration Patterns
🎯 Why GraphQL with Modern Hooks?
❌ REST API Problems
Multiple Requests
Need 3 API calls for user profile
// Multiple requests required
const user = await fetch('/api/users/1');
const posts = await fetch('/api/users/1/posts');
const followers = await fetch('/api/users/1/followers');
Over/Under Fetching
Get unnecessary data or make extra requests
// Getting too much data
{
"id": 1,
"name": "John",
"email": "john@example.com",
"address": { /* don't need this */ },
"preferences": { /* don't need this */ },
"metadata": { /* don't need this */ }
}
✅ GraphQL Solutions
Single Request
Get all related data in one query
// Single GraphQL query
query GetUserProfile($id: ID!) {
user(id: $id) {
name
email
posts {
title
content
}
followers {
name
}
}
}
Precise Data Fetching
Request exactly what you need
// Only get what you need
{
"user": {
"name": "John",
"email": "john@example.com",
"posts": [
{ "title": "Hello World", "content": "..." }
],
"followers": [
{ "name": "Jane" }
]
}
}
🎮 GraphQL + React Query Demo
🔧 GraphQL Query Builder
Select Fields:
Generated Query:
query GetUser($id: ID!) {
user(id: $id) {
id
name
}
}
📊 Query Results
🏆 GraphQL Best Practices
Query Optimization
- Use fragments for reusable field sets
- Implement query complexity analysis
- Add pagination to large datasets
- Use aliases to avoid conflicts
Caching Strategies
- Normalize cache with Apollo Client
- Use cache-first policies for static data
- Implement cache invalidation patterns
- Consider persisted queries
Security & Performance
- Implement query depth limiting
- Add rate limiting and timeout
- Use DataLoader for N+1 prevention
- Validate and sanitize inputs
Development Experience
- Use GraphQL Code Generator
- Implement proper error handling
- Add comprehensive logging
- Use GraphQL Playground/Studio
📡 Real-time Data Patterns
🔄 Real-time Integration Strategies
🔌 WebSockets
Persistent bidirectional connection for real-time updates
// WebSocket with React Query
function useRealtimeData() {
const queryClient = useQueryClient();
useEffect(() => {
const ws = new WebSocket('ws://localhost:8080');
ws.onmessage = (event) => {
const update = JSON.parse(event.data);
// Update specific query cache
queryClient.setQueryData(['data', update.id], update);
// Or invalidate to refetch
queryClient.invalidateQueries(['data']);
};
return () => ws.close();
}, [queryClient]);
}
✅ Pros
- True real-time updates
- Bidirectional communication
- Low latency
❌ Cons
- Connection management complexity
- Server resource usage
- Firewall/proxy issues
📡 Server-Sent Events (SSE)
Unidirectional server-to-client streaming
// SSE with React Query
function useSSEUpdates() {
const queryClient = useQueryClient();
useEffect(() => {
const eventSource = new EventSource('/api/events');
eventSource.onmessage = (event) => {
const data = JSON.parse(event.data);
// Update cache with new data
queryClient.setQueryData(['live-data'], (oldData) => [
data,
...oldData.slice(0, 9) // Keep last 10 items
]);
};
return () => eventSource.close();
}, [queryClient]);
}
✅ Pros
- Simple to implement
- Auto-reconnection
- HTTP-based
❌ Cons
- Unidirectional only
- Browser connection limits
- Text-only data
🔄 Polling
Regular intervals data fetching
// Smart polling with React Query
function useLiveData() {
return useQuery(
['live-data'],
fetchLiveData,
{
refetchInterval: (data) => {
// Adaptive polling based on data freshness
return data?.isActive ? 1000 : 30000;
},
refetchIntervalInBackground: true,
refetchOnWindowFocus: true
}
);
}
✅ Pros
- Simple implementation
- Works with any API
- Configurable intervals
❌ Cons
- Delayed updates
- Unnecessary requests
- Battery drain on mobile
🎮 Real-time Data Demo
🔗 Connection Status
📊 Live Data Feed
📈 Performance Metrics
🎯 API Integration Summary & Best Practices
📋 Key Takeaways
🎣 React Query Benefits
- Automatic caching and background updates
- Optimistic updates and error handling
- DevTools for debugging
- Infinite queries and pagination
- Offline support with persistence
📡 SWR Advantages
- Smaller bundle size (4.2kb)
- Simple API and learning curve
- Built-in focus revalidation
- TypeScript support
- Vercel ecosystem integration
🚀 GraphQL Power
- Single request for complex data
- Precise data fetching
- Strong typing with TypeScript
- Real-time subscriptions
- Introspection and tooling
🎯 Decision Framework
Choose React Query when:
- Complex caching requirements
- Optimistic updates needed
- Advanced mutation patterns
- Infinite scrolling/pagination
- Team prefers feature-rich solutions
Choose SWR when:
- Bundle size is critical
- Simple data fetching needs
- Focus revalidation important
- Vercel/Next.js ecosystem
- Team prefers simplicity
Choose GraphQL when:
- Complex data relationships
- Multiple clients (web, mobile)
- Strong typing requirements
- API evolution needed
- Real-time subscriptions
Stick with REST when:
- Simple CRUD operations
- Existing REST infrastructure
- Team unfamiliar with GraphQL
- Caching at HTTP level needed
- File uploads/downloads
🛠️ Implementation Guide
Setup & Configuration
// React Query setup
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 5 * 60 * 1000, // 5 minutes
cacheTime: 10 * 60 * 1000, // 10 minutes
retry: 3,
refetchOnWindowFocus: false,
},
},
});
function App() {
return (
<QueryClientProvider client={queryClient}>
<YourApp />
</QueryClientProvider>
);
}
Custom Hooks Pattern
// Custom hooks for reusability
export function useUser(userId) {
return useQuery({
queryKey: ['user', userId],
queryFn: () => api.getUser(userId),
enabled: !!userId,
});
}
export function useUpdateUser() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: api.updateUser,
onSuccess: (data, variables) => {
queryClient.setQueryData(['user', variables.id], data);
queryClient.invalidateQueries(['users']);
},
});
}
Error Handling & Loading States
// Comprehensive error handling
function UserProfile({ userId }) {
const {
data: user,
isLoading,
error,
isError
} = useUser(userId);
if (isLoading) return <UserSkeleton />;
if (isError) {
return (
<ErrorBoundary
error={error}
retry={() => queryClient.refetchQueries(['user', userId])}
/>
);
}
return <UserCard user={user} />;
}
🚀 Final Recommendations
🏗️ Architecture
- Design API schema thoughtfully
- Implement proper error boundaries
- Use TypeScript for type safety
- Plan for offline scenarios
⚡ Performance
- Implement request deduplication
- Use background refetching wisely
- Consider data normalization
- Monitor cache hit rates
🧪 Testing
- Mock API responses in tests
- Test loading and error states
- Verify cache behavior
- Test offline scenarios
🎉 Session Complete!
You've now mastered the four pillars of modern web rendering: