How To Fetch Data In React From REST API
Open this example in our interactive code editor to experiment with it.
Try the EditorIntroduction
Data fetching is one of the most common tasks in any React application. Whether you’re building a dashboard, a social media app, or a simple portfolio, at some point you’ll need to pull data from an external source.
In this article, we’ll explore different ways to fetch data in React by using the PokeAPI - a free, open REST API for Pokemon data. We’ll start with the built-in fetch API, then move to axios, create a custom hook, and finally look at react-query.
Let’s dive in!
Fetch PokeAPI data with hooks
It’s not that straightforward
You might think fetching data is as simple as calling fetch inside your component, but there are some gotchas. Let’s look at a naive approach first:
// Don't do this - it will cause an infinite loop!
const App = () => {
const [pokemon, setPokemon] = React.useState(null);
// This runs on every render, causing setState,
// which triggers another render...
fetch("https://pokeapi.co/api/v2/pokemon/pikachu")
.then((res) => res.json())
.then((data) => setPokemon(data));
return <div>{pokemon ? pokemon.name : "Loading..."}</div>;
};
The code above will cause an infinite loop because calling setPokemon triggers a re-render, which calls fetch again, which calls setPokemon again, and so on.
Adding useEffect with dependencies
The solution is to use useEffect to control when the fetch happens:
// Better, but still missing error and loading handling
const App = () => {
const [pokemon, setPokemon] = React.useState(null);
React.useEffect(() => {
fetch("https://pokeapi.co/api/v2/pokemon/pikachu")
.then((res) => res.json())
.then((data) => setPokemon(data));
}, []);
return <div>{pokemon ? pokemon.name : "Loading..."}</div>;
};
Warning: Make sure to include the dependency array
[]inuseEffect. Without it, the effect runs after every render, causing the same infinite loop problem.
Using an inner async function
Since we can’t make the useEffect callback itself async, we define an async function inside it:
const App = () => {
const [pokemon, setPokemon] = React.useState(null);
React.useEffect(() => {
const fetchPokemon = async () => {
const response = await fetch(
"https://pokeapi.co/api/v2/pokemon/pikachu"
);
const data = await response.json();
setPokemon(data);
};
fetchPokemon();
}, []);
return <div>{pokemon ? pokemon.name : "Loading..."}</div>;
};
Handling error and loading states
A production-ready component should handle loading and error states:
const App = () => {
const [pokemon, setPokemon] = React.useState(null);
const [loading, setLoading] = React.useState(true);
const [error, setError] = React.useState(null);
React.useEffect(() => {
const fetchPokemon = async () => {
try {
setLoading(true);
const response = await fetch(
"https://pokeapi.co/api/v2/pokemon/pikachu"
);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
setPokemon(data);
} catch (e) {
setError(e.message);
} finally {
setLoading(false);
}
};
fetchPokemon();
}, []);
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error}</p>;
return (
<div>
<h1>{pokemon.name}</h1>
<img src={pokemon.sprites.front_default} alt={pokemon.name} />
<p>Height: {pokemon.height}</p>
<p>Weight: {pokemon.weight}</p>
</div>
);
};
Creating a useApi hook with axios
As your application grows, you’ll want to reuse data fetching logic across multiple components. Let’s create a custom useApi hook using axios:
import { useState, useEffect } from "react";
import axios from "axios";
const useApi = (url) => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const source = axios.CancelToken.source();
const fetchData = async () => {
try {
setLoading(true);
const response = await axios.get(url, {
cancelToken: source.token,
});
setData(response.data);
} catch (e) {
if (!axios.isCancel(e)) {
setError(e.message);
}
} finally {
setLoading(false);
}
};
fetchData();
return () => {
source.cancel("Component unmounted");
};
}, [url]);
return { data, loading, error };
};
export default useApi;
Now you can use this hook in any component:
import useApi from "./useApi";
const PokemonCard = ({ name }) => {
const { data, loading, error } = useApi(
`https://pokeapi.co/api/v2/pokemon/${name}`
);
if (loading) return <p>Loading {name}...</p>;
if (error) return <p>Error loading {name}: {error}</p>;
return (
<div>
<h2>{data.name}</h2>
<img src={data.sprites.front_default} alt={data.name} />
<p>Height: {data.height}</p>
<p>Weight: {data.weight}</p>
</div>
);
};
const App = () => {
return (
<div>
<h1>Pokemon Cards</h1>
<PokemonCard name="pikachu" />
<PokemonCard name="charizard" />
<PokemonCard name="bulbasaur" />
</div>
);
};
export default App;
What if there’s something that already exists?
Writing custom hooks is great for learning, but in production you might want a battle-tested solution. Enter React Query (now TanStack Query).
React Query provides:
- Automatic caching - No duplicate requests for the same data
- Background refetching - Data stays fresh automatically
- Pagination and infinite scroll - Built-in support
- Optimistic updates - Update UI before server confirms
- DevTools - Inspect cache state and queries
Here’s how it looks:
import { QueryClient, QueryClientProvider, useQuery } from "react-query";
const queryClient = new QueryClient();
const fetchPokemon = async (name) => {
const response = await fetch(
`https://pokeapi.co/api/v2/pokemon/${name}`
);
if (!response.ok) {
throw new Error("Network response was not ok");
}
return response.json();
};
const PokemonCard = ({ name }) => {
const { data, isLoading, error } = useQuery(
["pokemon", name],
() => fetchPokemon(name)
);
if (isLoading) return <p>Loading {name}...</p>;
if (error) return <p>Error: {error.message}</p>;
return (
<div>
<h2>{data.name}</h2>
<img src={data.sprites.front_default} alt={data.name} />
</div>
);
};
const App = () => (
<QueryClientProvider client={queryClient}>
<div>
<h1>Pokemon with React Query</h1>
<PokemonCard name="pikachu" />
<PokemonCard name="charizard" />
<PokemonCard name="bulbasaur" />
</div>
</QueryClientProvider>
);
export default App;
Using a Grid Layout
When displaying multiple items, a grid layout makes your UI much cleaner. You can combine data fetching with a responsive CSS Grid:
const PokemonGrid = () => {
const pokemonNames = [
"pikachu", "charizard", "bulbasaur",
"squirtle", "jigglypuff", "eevee"
];
return (
<div style={{
display: "grid",
gridTemplateColumns: "repeat(auto-fill, minmax(200px, 1fr))",
gap: "16px",
padding: "16px"
}}>
{pokemonNames.map((name) => (
<PokemonCard key={name} name={name} />
))}
</div>
);
};
REST API, GraphQL, or gRPC?
Now that you know how to fetch data from a REST API, you might wonder about other options:
- REST API - The most common approach. Uses HTTP methods (GET, POST, PUT, DELETE) with multiple endpoints. Great for simple CRUD applications
- GraphQL - Uses a single endpoint with queries that define exactly what data you need. Prevents over-fetching and under-fetching. Check out Fetch Data In React With GraphQL for more details
- gRPC - Uses Protocol Buffers for high-performance, typed communication. Mostly used for server-to-server communication but can work in the browser via gRPC-Web
For most frontend applications, REST API or GraphQL will be your go-to choices. REST is simpler to get started with, while GraphQL shines when you have complex data requirements.
Summary
Here’s what we covered:
- fetch API - Built into the browser, no dependencies needed. Good for simple use cases
- useEffect - Essential for controlling when data fetching happens. Always include a dependency array
- Error and loading states - Always handle these in production code
- Custom hooks - Extract reusable data fetching logic with hooks like
useApi - axios - A popular HTTP client with features like request cancellation and interceptors
- React Query - A powerful library that handles caching, background refetching, and much more
- Grid layouts - Combine data fetching with responsive layouts for better UX
The key takeaway is to start simple and add complexity as needed. Begin with fetch and useEffect, and when your application grows, consider adopting React Query for a more robust solution.
Ready to level up your coding skills?
Build real projects and grow your portfolio with BigDevSoon.
Start 7-Day Free Trial
Creator of BigDevSoon
Full-stack developer and educator passionate about helping developers build real-world skills through hands-on projects. Creator of BigDevSoon, a vibe coding platform with 21 projects, 100 coding challenges, 40+ practice problems, and Merlin AI.
Related Posts
Fetch Data In React As User Types Or Clicks
Let's dive into a bit more complex examples of real-world scenarios. Fetching data from an API as a user types or clicks is a common pattern in modern web applications.
Fetch Data In React With GraphQL
GraphQL introduces totally new concept for data fetching. One endpoint to rule them all, let's see how we can use it in React.
8 Useful React Components
There is a lot of pre-built, reusable, abstracted, encapsulated, and tested code available to use nowadays.