SSR: Effects don’t run on the server. This means that initial server-rendered HTML will only include a loading state with no data. The client computer will have to download all JavaScript and render our app only to discover that now it needs to load the data. This is not very efficient.
Creates Network waterfalls: Fetching directly in Effects makes it easy to create “network waterfalls”. You render the parent component, it fetches some data, renders the child components, and then they start fetching their data. If the network is not very fast, this is significantly slower than fetching all data in parallel.
Avoids Preloading and caching: Fetching directly in Effects usually means you don’t preload or cache data. For example, if the component unmounts and then mounts again, it would have to fetch the data again.
It’s not very ergonomic. There’s quite a bit of boilerplate code involved when writing fetch calls in a way that doesn’t suffer from bugs like race conditions.
If you use a framework, use its built-in data fetching mechanism. Modern React frameworks have integrated data fetching mechanisms that are efficient and don’t suffer from the above pitfalls.
Otherwise, consider using or building a client-side cache. Popular open-source solutions include React Query, useSWR, and React Router 6.4+. You can build your solution too, in which case you would use Effects under the hood but also add logic for deduplicating requests, caching responses, and avoiding network waterfalls (by preloading data or hoisting data requirements to routes).
You can continue fetching data directly in Effects if none of these approaches suit you.