React Hooks don't have built-in support for asynchronous operations, but you can handle them using async functions, Promises, or libraries like axios and fetch. Here’s how you can manage async operations effectively.
useEffect for Side Effects (Fetching Data)Since useEffect runs after render, it’s a great place to fetch data from an API.
fetchimport { useState, useEffect } from "react";
function FetchData() {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchData = async () => {
try {
const response = await fetch("https://jsonplaceholder.typicode.com/posts/1");
if (!response.ok) throw new Error("Failed to fetch");
const result = await response.json();
setData(result);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
fetchData();
}, []); // Empty dependency array → runs once
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error}</p>;
return <p>Title: {data?.title}</p>;
}
useEffect to run the request when the component mounts.async/await inside a function (not directly in useEffect).State updates using useState are synchronous in React, but you can use useEffect to react to state changes asynchronously.
import { useState, useEffect } from "react";
function AsyncCounter() {
const [count, setCount] = useState(0);
const [doubleCount, setDoubleCount] = useState(0);
useEffect(() => {
const timer = setTimeout(() => {
setDoubleCount(count * 2);
}, 500);
return () => clearTimeout(timer); // Cleanup
}, [count]); // Runs when count changes
return (
<div>
<button onClick={() => setCount(count + 1)}>Increment</button>
<p>Count: {count}</p>
<p>Double (delayed): {doubleCount}</p>
</div>
);
}
useEffect?useCallback for Asynchronous FunctionsIf you're passing an async function as a prop to child components, wrap it in useCallback to prevent unnecessary re-renders.
import { useState, useCallback } from "react";
function FetchButton({ fetchData }) {
return <button onClick={fetchData}>Fetch Data</button>;
}
function ParentComponent() {
const [data, setData] = useState(null);
const fetchData = useCallback(async () => {
const response = await fetch("https://jsonplaceholder.typicode.com/posts/2");
const result = await response.json();
setData(result);
}, []);
return (
<div>
<FetchButton fetchData={fetchData} />
<p>Data: {data?.title}</p>
</div>
);
}
useCallback?fetchData.useReducer for Complex Async State LogicFor managing complex async state (e.g., loading, success, failure states), use useReducer.
import { useReducer, useEffect } from "react";
const initialState = {
loading: true,
data: null,
error: null,
};
function reducer(state, action) {
switch (action.type) {
case "SUCCESS":
return { loading: false, data: action.payload, error: null };
case "ERROR":
return { loading: false, data: null, error: action.payload };
default:
return state;
}
}
function FetchWithReducer() {
const [state, dispatch] = useReducer(reducer, initialState);
useEffect(() => {
async function fetchData() {
try {
const response = await fetch("https://jsonplaceholder.typicode.com/posts/3");
const result = await response.json();
dispatch({ type: "SUCCESS", payload: result });
} catch (err) {
dispatch({ type: "ERROR", payload: err.message });
}
}
fetchData();
}, []);
if (state.loading) return <p>Loading...</p>;
if (state.error) return <p>Error: {state.error}</p>;
return <p>Title: {state.data?.title}</p>;
}
useReducer?useContext for Asynchronous Global StateFor fetching data once and sharing it globally, use useContext with a provider.
import { createContext, useContext, useState, useEffect } from "react";
const DataContext = createContext();
function DataProvider({ children }) {
const [data, setData] = useState(null);
useEffect(() => {
async function fetchData() {
const response = await fetch("https://jsonplaceholder.typicode.com/posts/4");
const result = await response.json();
setData(result);
}
fetchData();
}, []);
return <DataContext.Provider value={data}>{children}</DataContext.Provider>;
}
function DisplayData() {
const data = useContext(DataContext);
return <p>Title: {data?.title || "Loading..."}</p>;
}
function App() {
return (
<DataProvider>
<DisplayData />
</DataProvider>
);
}
useContext?| Best Practice | Do This |
|---|---|
Use useEffect for API calls |
Fetch data inside useEffect |
Use async inside functions, not useEffect directly |
Define async functions inside useEffect |
| Handle errors properly | Use try/catch and setError states |
| Cleanup side effects | Return a cleanup function inside useEffect |
Use useCallback for async functions in props |
Prevent unnecessary re-renders |
Use useReducer for complex state updates |
Manage loading, success, and error states |