API Integration & Modern Hooks

React Query • SWR • GraphQL • Modern Data Fetching Patterns

⏱️ 11 minutes 🎣 React Query Focus 🔄 Real-time Examples

🔗 Modern Data Fetching Evolution

📈 From Callbacks to Modern Hooks

2010s

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);
    });
  });
});
Hard to read Error handling nightmare No caching
2015

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));
Better error handling Chainable
2017

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);
  }
}
Readable code Easy error handling
2020+

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} />;
}
Automatic caching Background updates Loading states Error boundaries

🚀 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.

🧠 Intelligent caching
🔄 Background refetching
📱 Window focus refetching
Optimistic updates

🎮 Interactive React Query Demo

🔍 Query States

Loading: false
Error: false
Fetching: false
Stale: false

💾 Cache Status

No cached data

📊 Fetched Data

Click a fetch button to load data

⏱️ Request Timeline

Ready Waiting for user interaction

🔄 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

Bundle: ~4.2kb Weekly Downloads: 3M+ GitHub Stars: 27k+
VS

React Query

Powerful data synchronization for React by TanStack

Bundle: ~12.8kb Weekly Downloads: 2M+ GitHub Stars: 35k+

🔍 Feature-by-Feature Comparison

Feature SWR React Query
Bundle Size 4.2kb ✅ 12.8kb
Caching Strategy SWR Pattern Advanced ✅
Mutations Basic Advanced ✅
Optimistic Updates Manual Built-in ✅
DevTools Basic Excellent ✅
Background Refetch Excellent ✅ Excellent ✅
Infinite Queries Manual Built-in ✅
Learning Curve Easy ✅ Moderate

🎮 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>;
}
Click fetch to see SWR in action

🎣 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>
  );
}
Click fetch to see React Query in action

📊 Performance Comparison

Cache Hits: SWR: 0 RQ: 0
Network Requests: SWR: 0 RQ: 0
Background Updates: SWR: 0 RQ: 0

🚀 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

Query Size: -
Response Size: -
Execution Time: -
Execute a query to see 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

WebSocket: Disconnected
SSE: Disconnected
Polling: Stopped

📊 Live Data Feed

Connect to a data source to see live updates

📈 Performance Metrics

Updates/sec: 0
Total Updates: 0
Avg Latency: -
Cache Efficiency: -

🎯 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

1

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>
  );
}
2

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']);
    },
  });
}
3

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:

💻 CSR Interactive apps
🖥️ SSR SEO & performance
📄 SSG Speed & scale
🔗 API Hooks Data sync