2023 Update: Load data on the server or use React Query
The newly-written official docs provides an example of how to fetch data with useEffect
without race condition issues.
If you're using a metaframework like Next.js or Remix, they allow for data fetching on the server and passing the data to the components as props, which can eliminate the need for fetching data on the client. This tends to be better for UX and performance due elimination of the traditional fetch-after-you-render model, or prefetching data needed for the next page in the case of transitions.
If you want a pure client-side solutions which work well with Suspense:
- React Query is the most popular declarative data fetching library for React that provides useful features like caching, refetching, prefetching, etc.
- SWR is a more lightweight option.
2021 Update: Suspense
As Dan Abramov said on this GitHub Issue:
Longer term we'll discourage this (useEffect) pattern because it encourages race conditions. Such as — anything could happen between your call starts and ends, and you could have gotten new props. Instead, we'll recommend Suspense for data fetching
Yes, there's a similar (but not the same!) substitute for componentDidMount
with hooks, and it's the useEffect
hook.
The other answers don't really answer your question about where you can make API calls. You can make API calls by using useEffect
and passing in an empty array or object as the second argument as a replacement for componentDidMount()
. The key here is the second argument. If you don't provide an empty array or object as the second argument, the API call will be called on every render, and it effectively becomes a componentDidUpdate
.
As mentioned in the docs:
Passing in an empty array [] of inputs tells React that your effect doesn’t depend on any values from the component, so that effect would run only on mount and clean up on unmount; it won’t run on updates.
Here are some examples for scenarios where you will need to make API calls:
API Call Strictly on Mount
Try running the code below and see the result.
function User() {
const [firstName, setFirstName] = React.useState(null);
const [lastName, setLastName] = React.useState(null);
React.useEffect(() => {
fetch('https://randomuser.me/api/')
.then(results => results.json())
.then(data => {
const {name} = data.results[0];
setFirstName(name.first);
setLastName(name.last);
});
}, []); // <-- Have to pass in [] here!
return (
<div>
Name: {!firstName || !lastName ? 'Loading...' : `${firstName} ${lastName}`}
</div>
);
}
ReactDOM.render(<User />, document.querySelector('#app'));
<script src="https://unpkg.com/react@16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.development.js"></script>
<div id="app"></div>
API Call Whenever Some Prop/State Changes
If you are for example displaying a profile page of a user where each page has a userID state/prop, you should pass in that ID as a value into the second parameter of useEffect
so that the data will be refetched for a new user ID. componentDidMount
is insufficient here as the component might not need remounting if you go directly from user A to user B's profile.
In the traditional classes way, you would do:
componentDidMount() {
this.fetchData();
}
componentDidUpdate(prevProps, prevState) {
if (prevState.id !== this.state.id) {
this.fetchData();
}
}
With hooks, that would be:
useEffect(() => {
this.fetchData();
}, [id]);
Try running the code below and see the result. Change the id to 2 for instance to see that useEffect
is run again.
function Todo() {
const [todo, setTodo] = React.useState(null);
const [id, setId] = React.useState(1);
React.useEffect(() => {
if (id == null || id === '') {
return;
}
fetch(`https://jsonplaceholder.typicode.com/todos/${id}`)
.then(results => results.json())
.then(data => {
setTodo(data);
});
}, [id]); // useEffect will trigger whenever id is different.
return (
<div>
<input value={id} onChange={e => setId(e.target.value)}/>
<br/>
<pre>{JSON.stringify(todo, null, 2)}</pre>
</div>
);
}
ReactDOM.render(<Todo />, document.querySelector('#app'));
<script src="https://unpkg.com/react@16.8.1/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.8.1/umd/react-dom.development.js"></script>
<div id="app"></div>
You should read up on useEffect
so that you know what you can/cannot do with it.