Separate React render logic and transient state

This one might be obvious, but I see this pattern pop up often in the wild. Consider the following component, Foo, which loads data and renders an emoji based on said data:

tsx
1function Foo() {
2 const { data, isLoading, isError } = useQuery(clientFunction);
3
4 if (isLoading) return <Skeleton />;
5 if (isError) return <Error />;
6
7 return <div>{data.baz === true ? '✅' : '❌'}</div>;
8}

There are a few problems with this component. The first is it’s difficult to test. We must mock clientFunction or useQuery to return the result we want to test, and ensure that transient states (loading/error) are no longer visible by the time we want to test.

Our component has 4 potential outcomes. We might render a Skeleton, an Error, a  or a . Depending on what other logic might live in our component, these different paths can muddy things up quite a bit and make the component harder to reason about.

A cleaner approach would be to split the transient states out from the core component, like so:

jsx
1function Foo() {
2 const { data, isLoading, isError } = useQuery(clientFunction);
3
4 if (isLoading) return <Skeleton />;
5 if (isError) return <Error />;
6
7 return <Baz value={data.baz} />;
8}
9
10function Baz({ value }) {
11 return <div>{value === true ? '✅' : '❌'}</div>;
12}

Testing the part we really care about – the component itself – becomes dead simple. Simply test <Baz value={...} />and now your unit tests don’t need to worry about mocking any client functions or query hooks. As a bonus, if the mechanism we use to query data changes (e.g., data is fetched server-side and passed to the client), the component does not need an update.

Your render logic is now decoupled from the mechanism used to fetch its props, which keeps your components portable, testable, and simple.