Requests in a component's body
Why you can't make requests in a component's body
1. A component can render many times
React calls the component like a regular function on every render:
function User() {
console.log('render');
// ...
return <div>...</div>;
}If you write a request inside the component's body:
function User() {
fetch('/api/user') // wrong
return <div>...</div>;
}this fetch() will run on every render, and there can be dozens of those:
- due to a
statechange; - due to a
propschange; - due to context;
- due to a parent's render;
- in React 18, even twice in dev mode (Strict Mode).
As a result, you get a bunch of duplicate requests, network load, and a "flickering" interface.
2. React requires pure components
A component in React must be a pure function:
the same function with the same inputs (props/state) must always return the same JSX without side effects.
An async request (fetch, axios, localStorage, WebSocket) is a side effect:
it changes the outside world, makes an HTTP call, and isn't deterministic.
React expects:
render(props) → JSXnot:
render(props) → side effect + JSXSo such operations need to be moved into effects (useEffect), which React runs after the render,
when it's safe to perform asynchronous actions.
3. Async operations break the render phase
If you do await right in the body:
async function User() {
const res = await fetch('/api/user'); // wrong
const user = await res.json();
return <p>{user.name}</p>;
}React won't wait for that await. It will just see that the component returned a Promise instead of JSX and throw an error.
The exception is React Server Components (RSC) in Next.js 13+:
there, server components can genuinely be async,
because the render happens on the server, where React can "wait" for await.
But in client components (regular React apps) this is not possible.
4. The risk of an infinite loop
If you write the request's result into state right in the body:
function User() {
const [data, setData] = useState();
fetch('/api/user')
.then(res => res.json())
.then(setData);
return <p>{data?.name}</p>;
}then every setData() triggers a new render, which triggers fetch() again, which triggers setData() again...
An infinite loop of requests and a frozen UI.
The right way
Use useEffect():
function User() {
const [data, setData] = useState(null);
useEffect(() => {
fetch('/api/user')
.then(res => res.json())
.then(setData)
.catch(console.error);
}, []); // empty dependency array = runs once on mount
if (!data) return <p>Loading...</p>;
return <p>{data.name}</p>;
}Summary
| Making the request in the component's body | Making the request in useEffect |
|---|---|
| Runs on every render | Runs after mounting |
| Creates a side effect during the render | The side effect runs in a controlled way |
| Can lead to infinite requests | Runs once or on the needed dependencies |
| Violates the "pure function" principle | Follows the React "render -> effect" paradigm |
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.