Managing data fetching is a sometimes understated part of the frontend architecture. It can have huge effects on performance, but also developer experience (as well as sanity).
Handling mutations, errors, parallelization or request cancellations can create a lot of boilerplate, hard-to-maintain, and error-prone code, copied and pasted all over the place instead of being encapsulated. You wouldn’t let your business logic look like its seen better days, so why would you make an exception when doing calls to your API?
The library react-query offers a great solution for that problem. Learn about our approach of combining it with REST, OpenAPI and adding Suspense into the mix. You can use react-query with GraphQL as well, but I’ll be focusing on the REST / OpenAPI approach this time around.
react-query + REST + OpenApi + Suspense = a winning combination
What problems will this combo solve?
You will get a battle tested, opinionated way of fetching data that is safely typed using standard specification. No more manual typing of server objects, no more manually typing out axios clients with inlined authorization headers and payload creation, no more writing boilerplate to handle loading, errors and such. Caching? It comes out of the box! So no unnecessary re-rendering when you fetch if your data hasn’t changed. Mutating something? Query data gets updated without refetching the query. And a lot, lot more..
OK, LET’S START:
First of all, what is React Query?
React Query is a fairly popular data fetching library for React that makes fetching, caching, synchronizing and updating server state pretty enjoyable. You don’t need to repeat various code snippets to handle loading, errors, success states and such. In certain instances, It removes the need for state management tools like Redux, or even React Context for storing server data, as React Query handles it through its cache. You can prefetch data, you can seamlessly update query data on mutation without having to refetch the same query, as well as a host of other niceties that come with it.
What is OpenApi?
OpenApi Specification, or Swagger as it used to be called, is an open standard format for defining structure and syntax of REST APIs. It is both machine and human-readable which makes it easy to figure out how an API works when you try to consume it. There’s a lot of tooling within the OpenApi ecosystem which means you can use it regardless of what your tools of trade are — you’re most likely covered. What you get from adhering to it is a typed, consistent, nicely structured and easily pluggable API in return.
What is Suspense?
From React’s official documentation: Suspense is a React component that displays a fallback until its children have finished loading. You can wrap any part of your application with a Suspense component. If either data or code in its children hasn’t loaded yet, React will switch to rendering the fallback prop instead. It’s perfect for asynchronous data loading. Suspense comes together with a component called ErrorBoundary. The cool thing about ErrorBoundary is that it catches Javascript errors in their children components and it displays error fallback UI.
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
// Update state so the next render will show the fallback UI.
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
// You can also log the error to an error reporting service
logErrorToMyService(error, errorInfo);
}
render() {
if (this.state.hasError) {
// You can render any custom fallback UI
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
}
It is using two lifecycle methods static getDerivedStateFromError() which gets invoked after an error has been thrown by a child component and should return value to update state and componentDidCatch() which is mainly used for logging errors.
When you combine Suspense and ErrorBoundary you get
<ErrorBoundary fallback={<ErrorLayout />}>
<Suspense fallback={<ProductListSkeleton />}>
<ProductList />
</Suspense>
</ErrorBoundary>;
So once you get your Suspense and ErrorBoundary in place it’s time to generate type safe clients.
Generating React Query Hooks:
Hooks can be generated by using: https://www.npmjs.com/package/openapi-typescript-codegen?activeTab=readme
Steps to generate:
yarn add —dev @openapi-codegen/{cli,typescript}
npx openapi-codegen init
- Select Url
- Paste the OpenApi spec ( see e.g. https://github.com/OAI/OpenAPI-Specification/blob/main/examples/v3.0/api-with-examples.json )
- Enter namespace (can be your API’s name or whatever)
- Select react-query components
- Enter the path where you want your generated folder (I’d usually pick src/api)
- Execute
npx openapi-codegen gen <namespace>
Now you can inspect what we have at the path we specified.
Components file is where you get your custom hooks, for example:
export const useProducts = <TData = Schemas.Products>(
variables: ProductsVariables,
options?: Omit<
reactQuery.UseQueryOptions<Schemas.Products, ProductsError, TData>,
"queryKey" | "queryFn"
>
) => {
const { fetcherOptions, queryOptions, queryKeyFn } = useApiContext(options);
return (
reactQuery.useQuery < Schemas.Products,
ProductsError,
TData >
(queryKeyFn({
path: "/api/products",
operationId: "productsResult",
variables,
}),
({ signal }) =>
fetchProducts({ ...fetcherOptions, ...variables }, signal),
{
...options,
...queryOptions,
})
);
};
Query params interface is under variables: ProductsVariables
What you get in response is ProductsResponse
Now you just import that hook wherever you want and make a query with it.
Example:
import { useProducts } from "@/api/apiComponents";
const { data } = useProducts(
{
queryParams: {
// insert your query params here
},
},
{ suspense: true }
);
If you hover over the queryParams you get to see what the interface is called.
Hover over data and you get to see what the response interface is called.
All of a sudden fetching in useEffect with manually typed data seems dangerous for performance, possibly inconsistent data between server and client, more code than needed and a lot more time wasted than you actually need to.
To sum it all up
You need an OpenApi spec that you get from your backend colleague and 10 mins of your time and your life as a frontend developer becomes a lot more easier.