Skip to main content

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:

javascript
function User() { console.log('render'); // ... return <div>...</div>; }

If you write a request inside the component's body:

javascript
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 state change;
  • due to a props change;
  • 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:

javascript
render(props)JSX

not:

javascript
render(props) → side effect + JSX

So 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:

javascript
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:

javascript
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():

javascript
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 bodyMaking the request in useEffect
Runs on every renderRuns after mounting
Creates a side effect during the renderThe side effect runs in a controlled way
Can lead to infinite requestsRuns once or on the needed dependencies
Violates the "pure function" principleFollows the React "render -> effect" paradigm

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.