Using useEffect for Fetch

Master data fetching with React's useEffect hook

๐ŸŽฃ What is useEffect for Fetching?

useEffect is a React hook that runs code after your component renders. It's commonly used to fetch data when a component first appears or when specific values change.


// Basic useEffect fetch pattern
'use client'
import { useEffect, useState } from 'react'

export default function Component() {
  const [data, setData] = useState(null)
  
  useEffect(() => {
    fetch('/api/data')
      .then(res => res.json())
      .then(setData)
  }, []) // Empty array = run once on mount
  
  return <div>{data?.title}</div>
}
                                    

Key Concepts

๐Ÿ”„

Component Lifecycle

Runs after component renders

useEffect(() => {
  // Runs after render
}, [])
๐Ÿ“ฆ

Dependency Array

Controls when effect runs

useEffect(() => {
  fetchData(id)
}, [id]) // Runs when id changes
๐Ÿงน

Cleanup Function

Prevents memory leaks

useEffect(() => {
  return () => cleanup()
}, [])
โš ๏ธ

Async Handling

Proper async/await usage

useEffect(() => {
  const load = async () => {}
  load()
}, [])

๐Ÿ”น Basic useEffect Fetch

The most common pattern is fetching data when the component first mounts. The empty dependency array ensures the effect runs only once.

'use client'
import { useState, useEffect } from 'react'

export default function BlogPost() {
  const [post, setPost] = useState(null)
  const [loading, setLoading] = useState(true)

  useEffect(() => {
    fetch('/api/posts/1')
      .then(response => response.json())
      .then(data => {
        setPost(data)
        setLoading(false)
      })
  }, []) // Empty array = run once on mount

  if (loading) return <p>Loading...</p>

  return (
    <article>
      <h1>{post.title}</h1>
      <p>{post.content}</p>
    </article>
  )
}

๐Ÿ”น Fetch with Dependencies

When you need to refetch data based on changing values, add those values to the dependency array. The effect will run whenever dependencies change.

'use client'
import { useState, useEffect } from 'react'

export default function UserProfile({ userId }) {
  const [user, setUser] = useState(null)

  useEffect(() => {
    // This runs when userId changes
    fetch(`/api/users/${userId}`)
      .then(res => res.json())
      .then(data => setUser(data))
  }, [userId]) // Runs when userId changes

  return (
    <div>
      <h2>{user?.name}</h2>
      <p>{user?.email}</p>
    </div>
  )
}

๐Ÿ”น Using Async/Await

You cannot make useEffect itself async, but you can create an async function inside it. This makes your code cleaner and easier to read.

'use client'
import { useState, useEffect } from 'react'

export default function Products() {
  const [products, setProducts] = useState([])
  const [error, setError] = useState(null)

  useEffect(() => {
    // Create async function inside useEffect
    const fetchProducts = async () => {
      try {
        const response = await fetch('/api/products')
        if (!response.ok) throw new Error('Failed to fetch')
        const data = await response.json()
        setProducts(data)
      } catch (err) {
        setError(err.message)
      }
    }

    fetchProducts()
  }, [])

  if (error) return <p>Error: {error}</p>

  return (
    <ul>
      {products.map(product => (
        <li key={product.id}>{product.name}</li>
      ))}
    </ul>
  )
}

๐Ÿ”น Cleanup Function

Cleanup functions prevent memory leaks by canceling ongoing operations when the component unmounts or before the effect runs again.

'use client'
import { useState, useEffect } from 'react'

export default function LiveData() {
  const [data, setData] = useState(null)

  useEffect(() => {
    let cancelled = false

    const fetchData = async () => {
      const response = await fetch('/api/live-data')
      const result = await response.json()
      
      // Only update state if not cancelled
      if (!cancelled) {
        setData(result)
      }
    }

    fetchData()

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

  return <div>{data?.value}</div>
}

๐Ÿ”น Multiple Dependencies

You can track multiple values in the dependency array. The effect runs whenever any of the dependencies change.

'use client'
import { useState, useEffect } from 'react'

export default function SearchResults({ query, category }) {
  const [results, setResults] = useState([])

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

    const searchProducts = async () => {
      const response = await fetch(
        `/api/search?q=${query}&category=${category}`
      )
      const data = await response.json()
      setResults(data)
    }

    searchProducts()
  }, [query, category]) // Runs when either changes

  return (
    <div>
      <p>Found {results.length} results</p>
      {results.map(item => (
        <div key={item.id}>{item.name}</div>
      ))}
    </div>
  )
}

๐Ÿง  Test Your Knowledge

What does an empty dependency array [] in useEffect mean?