Falytom logoFALYTOM
Games
ToolsUI ComponentsServicesProductsAbout
LoginSign Up
menu
  1. Home
  2. /Web Development
  3. /React
  4. /Data Fetching
Your Progress0%
0 of 42 completed

React Topics

Getting Started

  • What is React?
  • React vs Vanilla JavaScript
  • Setting Up Your Environment
  • Your First React App

JSX Fundamentals

  • Introduction to JSX
  • JSX Expressions and Variables
  • Conditional Rendering
  • Lists and Keys
  • Styling in JSX

Components Basics

  • Function Components
  • Props
  • Children Prop
  • Props Destructuring
  • Component Composition

State Management

  • Introduction to State
  • useState Hook
  • State Updates and Re-renders
  • Multiple State Variables
  • State Best Practices

Events and Interactivity

  • Handling Events
  • Event Objects
  • Forms in React
  • Form Validation

Side Effects and Data

  • Introduction to useEffect
  • Data Fetching
  • Cleanup Functions
  • Dependency Arrays

Advanced Hooks

  • useRef Hook
  • useContext Hook
  • useMemo Hook
  • useCallback Hook

React Patterns

  • Custom Hooks
  • Higher-Order Components
  • Render Props Pattern

React Router

  • Introduction to React Router
  • Routes and Navigation
  • Dynamic Routes and Parameters

Best Practices

  • Component Organization
  • Performance Optimization
  • Error Handling
  • Testing React Components

Real World Project

  • Building a Complete App

Data Fetching

Making API calls with useEffect

Most React applications need to fetch data from APIs - user profiles, product lists, blog posts, and more. Data fetching is one of the most common uses of useEffect. You'll learn how to make API calls, handle loading and error states, prevent race conditions, and build reusable data fetching patterns. Let's connect your React apps to the real world! 🌐

Basic Data Fetching

Here's the simplest way to fetch data when a component mounts:

basic-fetch.jsx
function UserProfile() {
  const [user, setUser] = React.useState(null)
  
  React.useEffect(() => {
    // Fetch data when component mounts
    fetch('https://jsonplaceholder.typicode.com/users/1')
      .then(response => response.json())
      .then(data => setUser(data))
  }, [])  // Empty array = fetch once on mount
  
  if (!user) {
    return <p>Loading...</p>
  }
  
  return (
    <div>
      <h1>{user.name}</h1>
      <p>Email: {user.email}</p>
      <p>Phone: {user.phone}</p>
    </div>
  )
}

// Flow:
// 1. Component renders (user is null)
// 2. Shows "Loading..."
// 3. useEffect runs fetch
// 4. Data arrives, setUser called
// 5. Component re-renders with user data

The Fetch API:

fetch() is a browser API for making HTTP requests.
• Returns a Promise
• First .then() converts response to JSON
• Second .then() receives the actual data
• Works in all modern browsers

Handling Loading State

Always show users when data is loading:

loading-state.jsx
function UserProfile() {
  const [user, setUser] = React.useState(null)
  const [isLoading, setIsLoading] = React.useState(true)
  
  React.useEffect(() => {
    setIsLoading(true)  // Start loading
    
    fetch('https://jsonplaceholder.typicode.com/users/1')
      .then(response => response.json())
      .then(data => {
        setUser(data)
        setIsLoading(false)  // Stop loading
      })
  }, [])
  
  if (isLoading) {
    return <p>Loading user data...</p>
  }
  
  return (
    <div>
      <h1>{user.name}</h1>
      <p>Email: {user.email}</p>
    </div>
  )
}

// Better UX - explicit loading state

Error Handling

Always handle errors - networks fail, APIs go down:

error-handling.jsx
function UserProfile() {
  const [user, setUser] = React.useState(null)
  const [isLoading, setIsLoading] = React.useState(true)
  const [error, setError] = React.useState(null)
  
  React.useEffect(() => {
    setIsLoading(true)
    setError(null)  // Clear previous errors
    
    fetch('https://jsonplaceholder.typicode.com/users/1')
      .then(response => {
        if (!response.ok) {
          throw new Error('Failed to fetch user')
        }
        return response.json()
      })
      .then(data => {
        setUser(data)
        setIsLoading(false)
      })
      .catch(error => {
        setError(error.message)
        setIsLoading(false)
      })
  }, [])
  
  if (isLoading) {
    return <p>Loading...</p>
  }
  
  if (error) {
    return <p style={{ color: 'red' }}>Error: {error}</p>
  }
  
  return (
    <div>
      <h1>{user.name}</h1>
      <p>Email: {user.email}</p>
    </div>
  )
}

// Three states: loading, error, success

āš ļø Response Status Check

fetch() only rejects on network errors!
It doesn't reject on 404, 500, etc.

Always check response.ok:
if (!response.ok) throw new Error('...')

Or check status code:
if (response.status !== 200) ...

Using Async/Await

Async/await makes code more readable than Promise chains:

async-await.jsx
function UserProfile() {
  const [user, setUser] = React.useState(null)
  const [isLoading, setIsLoading] = React.useState(true)
  const [error, setError] = React.useState(null)
  
  React.useEffect(() => {
    // Define async function inside useEffect
    const fetchUser = async () => {
      try {
        setIsLoading(true)
        setError(null)
        
        const response = await fetch('https://jsonplaceholder.typicode.com/users/1')
        
        if (!response.ok) {
          throw new Error(`HTTP error! status: ${response.status}`)
        }
        
        const data = await response.json()
        setUser(data)
      } catch (error) {
        setError(error.message)
      } finally {
        setIsLoading(false)
      }
    }
    
    fetchUser()  // Call the async function
  }, [])
  
  if (isLoading) return <p>Loading...</p>
  if (error) return <p>Error: {error}</p>
  
  return (
    <div>
      <h1>{user.name}</h1>
      <p>Email: {user.email}</p>
    </div>
  )
}

// Cleaner with async/await!
// Note: Can't make useEffect callback itself async

Why Not Async useEffect Directly?

āŒ This doesn't work:
useEffect(async () => { ... }, [])

useEffect expects a cleanup function or nothing.
Async functions return a Promise, not a function.

āœ… Instead, define async function inside:
useEffect(() => { async function fetch() {...} fetch() }, [])

Fetching Based on Props/State

Often you need to fetch data based on dynamic values:

fetch-with-params.jsx
function UserProfile({ userId }) {
  const [user, setUser] = React.useState(null)
  const [isLoading, setIsLoading] = React.useState(true)
  const [error, setError] = React.useState(null)
  
  React.useEffect(() => {
    const fetchUser = async () => {
      try {
        setIsLoading(true)
        setError(null)
        
        const response = await fetch(
          `https://jsonplaceholder.typicode.com/users/${userId}`
        )
        
        if (!response.ok) {
          throw new Error('User not found')
        }
        
        const data = await response.json()
        setUser(data)
      } catch (error) {
        setError(error.message)
      } finally {
        setIsLoading(false)
      }
    }
    
    fetchUser()
  }, [userId])  // Re-fetch when userId changes!
  
  if (isLoading) return <p>Loading user {userId}...</p>
  if (error) return <p>Error: {error}</p>
  
  return (
    <div>
      <h1>{user.name}</h1>
      <p>Email: {user.email}</p>
    </div>
  )
}

// Usage:
// <UserProfile userId={1} />
// When userId prop changes, effect re-runs and fetches new user

Preventing Race Conditions

Race conditions happen when requests return out of order:

race-condition-problem.jsx
// āŒ PROBLEM: Race condition
function UserProfile({ userId }) {
  const [user, setUser] = React.useState(null)
  
  React.useEffect(() => {
    fetch(`/api/users/${userId}`)
      .then(res => res.json())
      .then(data => setUser(data))
  }, [userId])
  
  return <div>{user?.name}</div>
}

// Scenario:
// 1. User clicks profile ID 1 (slow request, takes 3 seconds)
// 2. User clicks profile ID 2 (fast request, takes 1 second)
// 3. Response for ID 2 arrives first, shows user 2
// 4. Response for ID 1 arrives later, shows user 1 (wrong!)
// 
// Result: User clicked ID 2 but sees user 1!

Solution: Ignore Stale Requests

race-condition-solution.jsx
// āœ… SOLUTION: Use ignore flag
function UserProfile({ userId }) {
  const [user, setUser] = React.useState(null)
  const [isLoading, setIsLoading] = React.useState(true)
  
  React.useEffect(() => {
    let ignore = false  // Flag to ignore stale responses
    
    const fetchUser = async () => {
      setIsLoading(true)
      
      const response = await fetch(`/api/users/${userId}`)
      const data = await response.json()
      
      // Only update state if this is still the current request
      if (!ignore) {
        setUser(data)
        setIsLoading(false)
      }
    }
    
    fetchUser()
    
    // Cleanup: Mark this request as stale if userId changes
    return () => {
      ignore = true
    }
  }, [userId])
  
  if (isLoading) return <p>Loading...</p>
  return <div>{user.name}</div>
}

// How it works:
// 1. User clicks ID 1, ignore = false
// 2. User clicks ID 2, cleanup sets first ignore = true, new ignore = false
// 3. Response for ID 2 arrives, ignore is false, updates state
// 4. Response for ID 1 arrives, ignore is true, doesn't update state!

Complete Data Fetching Pattern

Here's a production-ready pattern with all best practices:

complete-pattern.jsx
function UserProfile({ userId }) {
  const [data, setData] = React.useState(null)
  const [isLoading, setIsLoading] = React.useState(true)
  const [error, setError] = React.useState(null)
  
  React.useEffect(() => {
    let ignore = false  // Prevent race conditions
    
    const fetchUser = async () => {
      try {
        setIsLoading(true)
        setError(null)
        
        const response = await fetch(
          `https://jsonplaceholder.typicode.com/users/${userId}`
        )
        
        if (!response.ok) {
          throw new Error(`HTTP error! status: ${response.status}`)
        }
        
        const data = await response.json()
        
        // Only update if this request is still relevant
        if (!ignore) {
          setData(data)
        }
      } catch (error) {
        // Only set error if this request is still relevant
        if (!ignore) {
          setError(error.message)
        }
      } finally {
        // Only update loading if this request is still relevant
        if (!ignore) {
          setIsLoading(false)
        }
      }
    }
    
    fetchUser()
    
    // Cleanup function
    return () => {
      ignore = true
    }
  }, [userId])  // Re-run when userId changes
  
  // Loading state
  if (isLoading) {
    return (
      <div>
        <p>Loading user {userId}...</p>
      </div>
    )
  }
  
  // Error state
  if (error) {
    return (
      <div>
        <p style={{ color: 'red' }}>Error: {error}</p>
        <button onClick={() => window.location.reload()}>
          Retry
        </button>
      </div>
    )
  }
  
  // Success state
  return (
    <div>
      <h1>{data.name}</h1>
      <p>Email: {data.email}</p>
      <p>Phone: {data.phone}</p>
      <p>Website: {data.website}</p>
    </div>
  )
}

// Production-ready pattern includes:
// āœ… Loading state
// āœ… Error handling
// āœ… Race condition prevention
// āœ… Cleanup function
// āœ… Proper dependencies
// āœ… User-friendly UI for all states

Interactive Demo

ReactPlayground.jsx
Code Editor
Preview

šŸ’” Tip: Edit the code above and click "Run" to see your changes

Custom useFetch Hook

Reuse data fetching logic by creating a custom hook:

custom-use-fetch.jsx
function useFetch(url) {
  const [data, setData] = React.useState(null)
  const [isLoading, setIsLoading] = React.useState(true)
  const [error, setError] = React.useState(null)
  
  React.useEffect(() => {
    let ignore = false
    
    const fetchData = async () => {
      try {
        setIsLoading(true)
        setError(null)
        
        const response = await fetch(url)
        
        if (!response.ok) {
          throw new Error(`HTTP error! status: ${response.status}`)
        }
        
        const data = await response.json()
        
        if (!ignore) {
          setData(data)
        }
      } catch (error) {
        if (!ignore) {
          setError(error.message)
        }
      } finally {
        if (!ignore) {
          setIsLoading(false)
        }
      }
    }
    
    fetchData()
    
    return () => {
      ignore = true
    }
  }, [url])  // Re-fetch when URL changes
  
  return { data, isLoading, error }
}

// Usage:
function UserProfile({ userId }) {
  const { data, isLoading, error } = useFetch(
    `https://jsonplaceholder.typicode.com/users/${userId}`
  )
  
  if (isLoading) return <p>Loading...</p>
  if (error) return <p>Error: {error}</p>
  
  return (
    <div>
      <h1>{data.name}</h1>
      <p>Email: {data.email}</p>
    </div>
  )
}

// Much cleaner! Reusable across components

Data Fetching Best Practices

  • Always handle loading state - Show users what's happening
  • Always handle errors - Networks fail, APIs go down
  • Check response.ok - Fetch doesn't reject on 404, 500, etc.
  • Prevent race conditions - Use ignore flag in cleanup
  • Use async/await - Cleaner than Promise chains
  • Include dependencies - Re-fetch when props/state change
  • Create custom hooks - Reuse fetching logic
  • Consider loading indicators - Spinners, skeletons, etc.
  • Provide retry mechanism - Let users retry failed requests
  • Cache when appropriate - Don't refetch unnecessarily
  • Consider libraries - React Query, SWR for advanced needs
  • Handle empty states - No data found messages

When to Use Data Fetching Libraries:

For complex apps with lots of data fetching, consider libraries like:
• React Query - Caching, refetching, mutations
• SWR - Stale-while-revalidate pattern
• RTK Query - With Redux Toolkit

These handle caching, deduplication, polling, and more automatically!

Key Takeaways

  • Use useEffect for data fetching on mount or when dependencies change
  • Always track loading, error, and data states
  • Use async/await inside useEffect (define function inside)
  • Check response.ok - fetch doesn't reject on HTTP errors
  • Prevent race conditions with ignore flag in cleanup function
  • Include URL/params in dependency array
  • Use try/catch for error handling
  • Create custom hooks to reuse fetching logic
  • Show loading indicators for better UX
  • Provide retry mechanism for failed requests
  • Consider data fetching libraries for complex apps
  • Data fetching is one of the most common useEffect use cases

What's Next?

You now know how to fetch data professionally in React! You can handle loading and error states, prevent race conditions, and build reusable fetching patterns.

In the next lesson, you'll learn about Cleanup Functions:

  • When and why to use cleanup functions
  • Preventing memory leaks
  • Cleaning up subscriptions and timers
  • Aborting fetch requests
  • Common cleanup patterns

Get ready to write bulletproof effects with proper cleanup! 🧹

Learning how to fetch data from APIs in React with useEffect!

Previous
Introduction to useEffect
Next
Cleanup Functions

Never Miss a New React Tutorial

Join 2,000+ developers learning React step-by-step. Get new tutorials, tips, and exclusive resources delivered to your inbox - completely FREE.

You might also like

UI Components

Ready-made components for your projects

Developer Tools

Boost productivity with handy generators

HTML Game

Learn HTML concepts through interactive gameplay

Join Our Web Development Training Program

Master frontend & backend development with hands-on projects.

React Tutorials

0 of 42 completed

Your Progress0%

Topics

Getting Started

  • What is React?
  • React vs Vanilla JavaScript
  • Setting Up Your Environment
  • Your First React App

JSX Fundamentals

  • Introduction to JSX
  • JSX Expressions and Variables
  • Conditional Rendering
  • Lists and Keys
  • Styling in JSX

Components Basics

  • Function Components
  • Props
  • Children Prop
  • Props Destructuring
  • Component Composition

State Management

  • Introduction to State
  • useState Hook
  • State Updates and Re-renders
  • Multiple State Variables
  • State Best Practices

Events and Interactivity

  • Handling Events
  • Event Objects
  • Forms in React
  • Form Validation

Side Effects and Data

  • Introduction to useEffect
  • Data Fetching
  • Cleanup Functions
  • Dependency Arrays

Advanced Hooks

  • useRef Hook
  • useContext Hook
  • useMemo Hook
  • useCallback Hook

React Patterns

  • Custom Hooks
  • Higher-Order Components
  • Render Props Pattern

React Router

  • Introduction to React Router
  • Routes and Navigation
  • Dynamic Routes and Parameters

Best Practices

  • Component Organization
  • Performance Optimization
  • Error Handling
  • Testing React Components

Real World Project

  • Building a Complete App
Falytom logoFALYTOM

Learn technologies like HTML, CSS, JavaScript, React, NodeJS and more through interactive games, tutorials, components and free tools. Transform your businesses with AI chatbots, SEO-optimized websites, and professional development services.

Ā© 2025 Tomilola Group
  • Facebook logo
  • Twitter logo
  • Instagram logo
  • LinkedIn logo
  • GitHub logo
  • YouTube logo