Code To Learn logo

Code To Learn

M3: Effects & Data

M3: Effects & Data Fetching

Connect to APIs, handle async operations, and manage loading states with useEffect.

Move beyond static data! Learn to fetch real data from APIs, handle loading states, and manage side effects with useEffect. 🌐


Overview

In Module 2, you worked with static data arrays. Now it's time to connect to a real API and fetch dynamic data!

This module introduces useEffect - React's hook for side effects. You'll learn to fetch data from an API, show loading spinners, handle errors gracefully, and prevent common async bugs like race conditions.

By the end, your app will display real property listings from a backend server instead of hardcoded arrays.


What You'll Build

Transform your static app into a dynamic, API-connected application:

📡 API Integration Connect to real backend and fetch property listings

⏳ Loading States Show spinner while data loads for better UX

❌ Error Handling Display helpful error messages when requests fail

🎠 Image Carousel Build interactive photo galleries for each property

🛡️ Race Condition Prevention Handle multiple concurrent requests safely


Key Concepts

useEffect Hook

The useEffect hook lets you perform side effects in functional components:

import { useEffect } from 'react';

function Component() {
   useEffect(() => {
      // Side effect code runs after render
      console.log('Component mounted!');
      
      // Optional cleanup function
      return () => {
         console.log('Component unmounting!');
      };
   }, []); // Dependencies array
   
   return <div>Content</div>;
}

Common side effects:

  • Fetching data from APIs
  • Setting up subscriptions
  • Manually changing the DOM
  • Setting up timers

Async/Await

Modern JavaScript syntax for handling asynchronous operations:

async function fetchData() {
   try {
      const response = await fetch('https://api.example.com/data');
      const data = await response.json();
      return data;
   } catch (error) {
      console.error('Failed to fetch:', error);
   }
}

Loading & Error States

Professional apps show feedback during async operations:

const [data, setData] = useState(null);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState(null);

if (isLoading) return <Spinner />;
if (error) return <Error message={error} />;
return <Data items={data} />;

Learning Objectives


Module Roadmap

LessonWhat You'll BuildConcepts Introduced
1Async state setupState initialization
2API fetchinguseEffect, async/await
3Loading trackingLoading state pattern
4Loading spinnerConditional rendering
5Error handlingTry/catch, error state
6Error displayUser feedback
7Race preventionAbortController, cleanup
8Code refactoringClean code patterns
9Image carouselComponent composition
10Module reviewBest practices

Prerequisites

Before starting Module 3, ensure:

  • ✅ Completed Module 2
  • ✅ HomePage has filter functionality
  • ✅ Understand useState hook
  • ✅ Comfortable with array methods
  • ✅ Basic understanding of APIs and HTTP

API We'll Use

This module connects to the Noroff Holidaze API:

Base URL: https://v2.api.noroff.dev
Endpoint: /holidaze/venues
Method: GET

Example response:

{
  "data": [
    {
      "id": "abc123",
      "name": "Cozy Beach House",
      "description": "Perfect seaside getaway",
      "media": [
        {
          "url": "https://images.unsplash.com/photo-123",
          "alt": "Beach house exterior"
        }
      ],
      "price": 150,
      "maxGuests": 4,
      "rating": 4.8,
      "location": {
        "city": "Miami",
        "country": "USA"
      }
    }
  ]
}

The useEffect Lifecycle

Understanding when effects run is crucial:

useEffect(() => {
   // Effect runs after EVERY render
});

useEffect(() => {
   // Effect runs ONCE on mount
}, []);

useEffect(() => {
   // Effect runs when 'dependency' changes
}, [dependency]);

useEffect(() => {
   // Setup
   const subscription = subscribeToData();
   
   // Cleanup runs before next effect or unmount
   return () => {
      subscription.unsubscribe();
   };
}, []);

Common Pitfalls

Missing dependency array causes infinite loops:

// ❌ BAD - runs after every render, causes infinite loop
useEffect(() => {
   fetch('/api/listings').then(data => setData(data));
});

// ✅ GOOD - runs once on mount
useEffect(() => {
   fetch('/api/listings').then(data => setData(data));
}, []);

Race conditions happen when multiple requests overlap:

// ❌ PROBLEM: If user changes filters quickly,
// old request might complete after new request
useEffect(() => {
   fetchListings(filters).then(setListings);
}, [filters]);

// ✅ SOLUTION: Cancel previous request
useEffect(() => {
   const controller = new AbortController();
   fetchListings(filters, controller.signal).then(setListings);
   return () => controller.abort();
}, [filters]);

What Makes This Module Important

Real-world applications need:

  • Dynamic data from servers
  • Loading indicators for UX
  • Error handling for reliability
  • Proper async patterns

After this module, you'll be able to:

  • Connect any React app to any API
  • Handle async operations confidently
  • Debug common useEffect issues
  • Build production-quality data fetching

Time Estimate

⏱️ 3-4 hours to complete all 10 lessons

  • Lessons 1-4 (Basic fetching): ~1 hour
  • Lessons 5-8 (Error handling & cleanup): ~1.5 hours
  • Lessons 9-10 (Carousel & review): ~1 hour

Let's Begin!

Ready to connect your app to a real API? Let's start by preparing the HomePage for async data fetching!