How to Test React.useEffect
Testing React.useEffect is much simpler than you think it is.


Let's analyze this React tree structure:
<App> <ShipSearch /> <ShipDetails /></App>
Let's assume that App renders the ShipSearch and ShipDetails components,
passing props as needed.
ShipSearch is a server component which takes a search and uses that to
perform a database query:
async function ShipSearch({ search }) { const shipResults = await searchShips({ search }) // ... render stuff}
The ShipDetails component is also a server component which takes a shipId
and uses that to perform a database query:
async function ShipDetails({ shipId }) { const ship = await getShip({ shipId }) // ... render stuff}
Because these are sibling components, both queries will run at the same time.
searchShips query ----> searchShips resultgetShip query ----> getShip result
That's all fine. But let's say the App component needs to resolve a logged in
user:
async function App() { const user = await getLoggedInUser() // ... render stuff}
Uh oh, now we have a waterfall:
getLoggedInUser query ----> getLoggedInUser result searchShips query ----> searchShips result getShip query ----> getShip result
The issue here is now App is waiting for getLoggedInUser to resolve before
it can render anything. This means both searchShips and getShip won't be
executed until after getLoggedInUser resolves, even though those don't
directly depend on the user.
This is a problem generally for React Suspense as well and I dive deep into that in the React Suspense workshop exercise 6.
There are a few ways to think about this problem:
Maybe it's not a problem if getLoggedInUser is fast 🤷♂️ But that's not always a
sure thing, and even if it's fast today doesn't mean someone won't add something
else that will be slow tomorrow. Still, it's always good to think about how bad
ignoring the problem would actually be since there could be bigger fish to fry.
Alternatively, we can lift searchShips and getShip into App and pass the
results down to ShipSearch and ShipDetails. Then we could use Promise.all
to make sure they run concurrently. But passing props can get annoying pretty
fast. We can use a library like
@epic-web/cachified to dedupe the
queries and then App simply kicks off the query earlier which would at least
be better.
Unfortunately, another issue though is this can get unweildy pretty fast,
especially if there's logic around which queries should run based on other
props. You'd have to move all that logic until your entire app lives in App
😱.
The next solution I can come up with is to use or build a compiler which can find all queries and preload them automatically. This is what Relay is. There are quite a few things I don't like about this approach though:
So I guess what I'm saying is I don't like any of these solutions. But I'd like to reframe the problem with more context.
Let's say we live in a world where we don't have RSCs or Suspense. You're just building an app with components that fetch data and render stuff.
In that world you've got three options:
Sound familiar? It's exactly the same problem we've always had with components and data. There's always been this tension between colocating and passing down data (prop drilling).
One of the nice things about Remix is that it gives you a fourth option:
This allows Remix to load data as soon as the request comes in regardless of whether the component has rendered.
This is what I've been doing for years and it's been awesome.
This blog post isn't really about why I think RSCs are awesome. You can read React Server Components: The Future of UI for that. Just know that composition at route boundaries is not as good as composition at component boundaries and I want React-level composition badly.
What I want to do is show how RSCs fit into the same tension between colocating and prop drilling that we've always had with components and data. There's no new problems here.
But what I find interesting is how a server-side waterfall is probably better than a client-side waterfall primarily because you get to control the network. The connection between your server rendering server and your database is probably stronger, faster, more reliable, and closer. Or maybe it's not, but the point is you are in control there and can make improvements to that if it's important to you. You also have more fine-grained control over caching (requests from separate clients can share a cache for common data).
When you have a client-side waterfall, you're dealing with the user's device and their network connection which may be great or may be terrible but you definitely have no control over.
So shifting this problem from the client to the server sounds like a net gain for many scenarios.
The "waterfall" problem of RSCs is not a new problem. It's the same tension between colocating and prop drilling that we've always had with components and data. Maybe there's a new solution we can look forward to in the future. I welcome ideas (preferrably something that doesn't require a special compiler)! Until then, I'm actually pretty happy with just making things fast enough that the problem isn't a problem.
Delivered straight to your inbox.
Testing React.useEffect is much simpler than you think it is.

How and why you should use CSS variables (custom properties) for theming instead of React context.

Why can't React just magically know what to do without a key?

Some common mistakes I see people make with useEffect and how to avoid them.
