Fetching Basics

Back

Loading concept...

🎣 Data Fetching in React: The Fishing Trip Adventure

Imagine you’re going fishing! You cast your line into the water (the internet), wait for a fish (data) to bite, and then reel it in. Sometimes you catch a big one. Sometimes the line snaps. Sometimes another fish grabs your bait before you’re ready!

That’s exactly what fetching data in React feels like.

Let’s learn how to become master fishers of data!


🎯 What You’ll Learn

  • How to fetch data when a component loads
  • How to show “Loading…” while waiting
  • How to handle errors gracefully
  • How to avoid race conditions (the sneaky problem!)
  • How to cancel requests you don’t need anymore
  • How to remember data you already fetched
  • How to make your app feel super fast

🪝 Fetch in useEffect: Casting Your Line

When your React component appears on screen, you often need data from the internet. Think of useEffect as your fishing rod that automatically casts when you sit down.

The Basic Pattern

function FishingTrip() {
  const [fish, setFish] = useState(null);

  useEffect(() => {
    // Cast your line!
    fetch('/api/fish')
      .then(res => res.json())
      .then(data => setFish(data));
  }, []); // Empty = cast once!

  return <div>{fish?.name}</div>;
}

What’s Happening?

  1. Component appears → useEffect runs
  2. fetch() sends a request to the internet
  3. When data arrives → setFish() saves it
  4. Component re-renders with your fish!

⚠️ The Empty Array []

That empty [] at the end is super important. It tells React: “Only run this once, when I first appear.”

Without it, your component would keep casting lines forever! 🎣🎣🎣


⏳ Loading States: The “Waiting” Sign

When you cast your line, there’s a moment of waiting. Your users need to know something is happening!

The Waiting Pattern

function FishingTrip() {
  const [fish, setFish] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    setLoading(true);
    fetch('/api/fish')
      .then(res => res.json())
      .then(data => {
        setFish(data);
        setLoading(false);
      });
  }, []);

  if (loading) {
    return <div>🎣 Casting line...</div>;
  }

  return <div>Caught: {fish?.name}!</div>;
}

Think of It Like This

State What User Sees
loading: true “Fishing…” spinner
loading: false The actual fish!

Always tell users what’s happening. Nobody likes staring at a blank screen!


💥 Error Handling: When the Line Snaps

Sometimes things go wrong. The internet is down. The server is busy. The fish got away!

Good fishers always have a backup plan.

The Safe Pattern

function FishingTrip() {
  const [fish, setFish] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    setLoading(true);
    setError(null);

    fetch('/api/fish')
      .then(res => {
        if (!res.ok) throw new Error('No fish!');
        return res.json();
      })
      .then(data => setFish(data))
      .catch(err => setError(err.message))
      .finally(() => setLoading(false));
  }, []);

  if (loading) return <div>🎣 Fishing...</div>;
  if (error) return <div>😢 {error}</div>;
  return <div>🐟 {fish?.name}</div>;
}

The Three States

graph TD A["Start Fetching"] --> B{Success?} B -->|Yes| C["Show Data"] B -->|No| D["Show Error"] C --> E["Done!"] D --> E

Remember: Always catch errors! Users should never see a broken page.


🏃 Race Conditions: The Sneaky Problem

Here’s a tricky situation. Imagine you search for “cat”, then quickly search for “dog”.

Two fishing lines in the water! Which fish arrives first?

The Problem

// User types: "cat" → "dog" quickly
// Request 1: /api/search?q=cat (slow)
// Request 2: /api/search?q=dog (fast)

// Dog results arrive first ✓
// Cat results arrive second...
// and OVERWRITE dog results! ❌

This is a race condition. The slow request wins even though you don’t want it anymore!

The Sneaky Timeline

Time →
User types "cat"     |------ Cat Request -------|→ Arrives LAST
User types "dog"  |-- Dog Request --|→ Arrives FIRST
                                    ↑
                              You see DOG here
                                          ↑
                                    Then CAT overwrites! 😱

We’ll fix this with request cancellation!


🚫 Request Cancellation: “Never Mind!”

Sometimes you need to tell the internet: “Stop! I don’t want that fish anymore!”

The Cleanup Pattern

useEffect(() => {
  let cancelled = false;

  fetch('/api/fish')
    .then(res => res.json())
    .then(data => {
      if (!cancelled) {
        setFish(data);
      }
    });

  // Cleanup function!
  return () => {
    cancelled = true;
  };
}, [searchTerm]);

How It Works

  1. User searches “cat” → starts request
  2. User searches “dog” → cleanup runs → cancelled = true
  3. Cat results arrive → check cancelled → it’s true! → ignore
  4. Dog results arrive → new request → cancelled = false → use it!

The cleanup function is your “cancel” button!


🛑 AbortController: The Professional Cancel

The simple cancelled flag works, but there’s a better way. AbortController actually STOPS the request mid-flight!

The Pro Pattern

useEffect(() => {
  const controller = new AbortController();

  fetch('/api/fish', {
    signal: controller.signal
  })
    .then(res => res.json())
    .then(data => setFish(data))
    .catch(err => {
      if (err.name !== 'AbortError') {
        setError(err.message);
      }
    });

  return () => controller.abort();
}, [searchTerm]);

Why AbortController is Better

Simple Flag AbortController
Ignores the response Actually stops the request
Data still downloads Saves bandwidth
Server still works Server can stop early too

The Flow

graph TD A["Create AbortController"] --> B["Pass signal to fetch"] B --> C{User navigates away?} C -->|Yes| D["Call abort"] D --> E["Request cancelled!"] C -->|No| F["Data arrives"] F --> G["Update state"]

📦 Caching Basics: Remember Your Catches

Imagine fishing at the same spot every day. Why catch the same fish over and over?

Caching means remembering what you already caught!

Simple Cache Pattern

const cache = {};

function useFish(id) {
  const [fish, setFish] = useState(
    cache[id] || null
  );
  const [loading, setLoading] = useState(
    !cache[id]
  );

  useEffect(() => {
    if (cache[id]) return; // Already have it!

    fetch(`/api/fish/${id}`)
      .then(res => res.json())
      .then(data => {
        cache[id] = data;
        setFish(data);
        setLoading(false);
      });
  }, [id]);

  return { fish, loading };
}

How It Works

graph TD A["Need fish &#35;5?"] --> B{In cache?} B -->|Yes| C["Return instantly!"] B -->|No| D["Fetch from server"] D --> E["Save to cache"] E --> F["Return data"]

Benefits of Caching

  • Faster - No waiting for repeat data
  • 💰 Cheaper - Fewer server requests
  • 🔋 Better UX - Instant navigation

🚀 Optimistic Updates: The Speed Trick

What if you could show results BEFORE the server responds?

Optimistic updates assume success and update the UI immediately!

The Magic Pattern

function LikeFish({ fish }) {
  const [likes, setLikes] = useState(fish.likes);

  async function handleLike() {
    // 1. Update NOW (optimistic!)
    setLikes(likes + 1);

    try {
      // 2. Tell server
      await fetch('/api/like', {
        method: 'POST',
        body: JSON.stringify({ id: fish.id })
      });
    } catch (error) {
      // 3. Oops! Roll back
      setLikes(likes);
    }
  }

  return (
    <button onClick={handleLike}>
      ❤️ {likes}
    </button>
  );
}

The Timeline

Normal Update:
Click → Wait... wait... wait... → Update UI (feels slow)

Optimistic Update:
Click → Update UI instantly! → Server confirms (feels instant!)

When to Use It

Good For Bad For
Likes/favorites Bank transfers
Adding to cart Deleting data
Toggle switches Critical operations

Rule: If failure is rare and reversible, be optimistic!


🎮 Putting It All Together

Here’s a complete example with everything we learned:

function FishSearch() {
  const [query, setQuery] = useState('');
  const [fish, setFish] = useState([]);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);

  useEffect(() => {
    if (!query) return;

    const controller = new AbortController();
    setLoading(true);
    setError(null);

    fetch(`/api/fish?q=${query}`, {
      signal: controller.signal
    })
      .then(res => res.json())
      .then(setFish)
      .catch(err => {
        if (err.name !== 'AbortError') {
          setError('Search failed!');
        }
      })
      .finally(() => setLoading(false));

    return () => controller.abort();
  }, [query]);

  return (
    <div>
      <input
        value={query}
        onChange={e => setQuery(e.target.value)}
        placeholder="Search fish..."
      />
      {loading && <p>🎣 Searching...</p>}
      {error && <p>😢 {error}</p>}
      {fish.map(f => <p key={f.id}>{f.name}</p>)}
    </div>
  );
}

📋 Quick Reference

Concept Purpose Key Code
Fetch in useEffect Load data on mount useEffect(() => fetch(), [])
Loading states Show waiting UI const [loading, setLoading] = useState(true)
Error handling Handle failures .catch(err => setError(err))
Race conditions Stale data problem Old requests override new
Request cancellation Ignore old requests let cancelled = false + cleanup
AbortController Stop requests properly controller.abort()
Caching Remember fetched data if (cache[id]) return cache[id]
Optimistic updates Instant UI feedback Update state before server

🎉 You Did It!

You now know how to:

✅ Fetch data when components load ✅ Show loading states while waiting ✅ Handle errors gracefully ✅ Understand and prevent race conditions ✅ Cancel requests you don’t need ✅ Use AbortController like a pro ✅ Cache data for speed ✅ Use optimistic updates for instant feedback

You’re ready to fetch data like a pro! 🎣🐟


“The best fishers aren’t the ones who catch the most fish—they’re the ones who know when to cast, when to wait, and when to let go.”

Loading story...

Story - Premium Content

Please sign in to view this story and start learning.

Upgrade to Premium to unlock full access to all stories.

Stay Tuned!

Story is coming soon.

Story Preview

Story - Premium Content

Please sign in to view this concept and start learning.

Upgrade to Premium to unlock full access to all content.