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