How to call REST api using Hooks in React
Jasser Mark Arioste
This tutorial shows us different ways of calling REST API correctly in React by using react hooks. We also explore the choices involved in what library to use in different scenarios when managing server state. One scenario is when a component loads and another one is after a certain action. We'll start with the most basic usage and then make our way up to using libraries to make our life easier. Let's learn some basics first.
What is server state in React? #
Server state is the state/data stored on the server (i.e., orders, users, settings), we use it to display data to the user and cache it on the client for performance reasons. Basically, anything that has to do with backend data is server state. So when calling an API, the data returned by the server is server state.
What NOT to do when calling APIs #
Let's start by showing what not to do when calling APIs in a Functional Component. At first thought you might be tempted to call fetch directly in a react functional component. And let's say after calling the api we update some state of our component
import * as React from 'react'; export default function App() { const [count, setCount] = React.useState(0); fetch('https://jsonplaceholder.typicode.com/users').then(() => { setCount(count + 1); }); return ( <div> <p>API called {count} times</p> </div> ); }
1234567891011121314
In the example above, it increments the count by one after calling a fake API. However we quickly realize that this results in an infinite loop. Once we set the state, it triggers a re-render of the component, which triggers another call to the api, which triggers another increment to the state, and then another re-render.
To do this properly in the most basic way, we have to use useEffect
hook. Let's explore how to do that in the next section.
Using useEffect
to call an API
#
Let's modify our code a bit and wrap our fetch call inside a useEffect
hook.
import * as React from 'react'; export default function App() { ... React.useEffect(() => { fetch('https://jsonplaceholder.typicode.com/users').then(() => { setCount(count + 1); }); }, []); ... }
12345678910111213
Now it is fixed and only calls the API once.
What does useEffect
do? Let's check it right from the react docs!
By using this Hook, you tell React that your component needs to do something after render. React will remember the function you passed (we’ll refer to it as our “effect”), and call it later after performing the DOM updates. In this effect, we set the document title, but we could also perform data fetching or call some other imperative API.- https://reactjs.org/docs/hooks-effect.html
What are the disadvantages of using useEffect
?
One problem of using useEffect
for api calls is that you have to manually track the data, loading and error states. You'll have to do this for all endpoints.
useEffect is fine for small apps or when you are still learning BUT sooner or later when your building real-world applications, you'll face problems like caching, deduplication, and so on. And for these problems we'll need more specialized solutions in dealing with server state.
Using useFetch
to call an API
#
In this section we'll use a more improved version of useEffect. We'll be using useFetch
from use-hooks
or usehooks-ts
libraries.
What features does useFetch
provide us?
It provides us with the following features:
- Fetching data
- Caching - either by memory or localStorage
- Automatically takes care of data and error states.
How to use useFetch
in React
First let's install usehook-ts
(if you're using typescript) or usehooks
yarn add usehooks-ts
1
Let's modify our component to use useFetch:
import React, { memo } from "react"; import { useFetch } from "usehooks-ts"; const Users = () => { const { data, error } = useFetch(`https://jsonplaceholder.typicode.com/users`); return ( <div> <pre>{JSON.stringify(data, null, 4)}</pre> {!!error && <div>{error?.message}</div>} </div> ); }; export default memo(Users);
123456789101112131415
Using useFetch
allows us to have a more descriptive api for our component and it handles the data and error states for us.
Should you use useFetch
in real-world apps?
Although it is an improvement from useEffect
, it's still insufficient for real-world applications. It also doesn't support SSR which is mandatory to have optimized SEO. The docs also recommends using other libraries like react-query
or swr
.
In the next section we'll move to using production-grade solutions for fetching server data in react.
Using useSWR
to call an API in React
#
SWR is a great solution for data fetching in react. The name “SWR” is derived from stale-while-revalidate, a HTTP cache invalidation strategy popularized by HTTP RFC 5861. It gives us more powerful features that the first two solutions didn't provide.
What are the features of SWR?
Below are the features of swr directly from their website:
- Fast, lightweight and reusable data fetching
- Built-in cache and request deduplication
- Real-time experience
- Transport and protocol agnostic
- SSR / ISR / SSG support
- TypeScript ready
- React Native
- Revalidation on focus
- Fast page navigation
- Polling on interval
- and many more
How to use useSWR
in react
The usage is identical to useFetch
, so if you use useFetch
you can easily swap swr for more features.
Lets install swr:
yarn add swr
1
Modify our component to use the useSWR
hook:
import React, { memo } from "react"; import useSWR from "swr"; //let's define a fetcher function that uses the fetch api const fetcher = (url: string) => fetch(url).then((res) => res.json()); const Users = () => { const { data, error } = useSWR(`/users?page=0&size=10`, fetcher); return ( <div> {data ? ( <pre>{JSON.stringify(data, null, 4)}</pre> ) : ( <div> loading... </div> )} {!!error && <div>{error?.message}</div>} </div> ); }; export default memo(Users); export default memo(Users);
1234567891011121314151617181920212223242526
The nice thing about defining fetcher function to fetch the data is that the implementation is not tightly coupled to the swr library. This is unlike useFetch which only uses fetch api from the browser. In swr, we can use any library we want like such as axios
, or grahpql-request
(for graphql queries) or just the native fetch api from the browser.
Should you use SWR for production-grade apps?
You should definitely consider using swr in production-grade applications. The features and flexibility it provides are more than enough reason to use this awesome data-fetching library.
The above example doesn't do swr justice, we haven't scratched the surface of how awesome this library is in terms of data-fetching in react. However, this tutorial will not dive-deep into swr as I think it deserves its own post.
Using useQuery
to call an API
#
useQuery
is the react hook from a more famous data-fetching library react-query
. Let's dive into it!
What are the features of react-query?
Based on npm documentation, below are the features:
- Transport/protocol/backend agnostic data fetching (REST, GraphQL, promises)
- Auto Caching + Refetching (stale-while-revalidate, Window Refocus, Polling/Realtime)
- Parallel + Dependent Queries
- Mutations + Reactive Query Refetching
- Multi-layer Cache + Automatic Garbage Collection
- Paginated + Cursor-based Queries
- Load-More + Infinite Scroll Queries w/ Scroll Recovery
- Request Cancellation
- React Suspense + Fetch-As-You-Render Query Prefetching
- Dedicated Devtools
- 13kb minizipped size
It's pretty much similar to swr in terms of features.
How to use useQuery in react
Usage is pretty similar to swr. But first let's install it!
yarn add @tanstack/react-query
1
Let's setup the queryClient
and QueryClientProvider
in a top-level component like _app.tsx
import type { AppProps } from "next/app"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; //create a query client const queryClient = new QueryClient(); function App({ Component, pageProps }: AppProps) { return ( <QueryClientProvider client={queryClient}> <Component {...pageProps} />; </QueryClientProvider> ); } export default App;
123456789101112131415
Let's modify our code above to use react-query:
import React from "react"; import { useQuery } from "@tanstack/react-query"; //let's define a fetcher function that uses the fetch api const fetcher = () => fetch(`/users?page=0&size=10`).then((res) => res.json()); interface User { id: number; firstName: string; lastName: string; maidenName: string; age: number; gender: string; email: string; } const Users = () => { // we can use generics for the useQuery hook to indicate // the data type and error type const { data, error } = useQuery<User[], Error>([`users`], fetcher); return ( <div> {data ? ( <pre>{JSON.stringify(data, null, 4)}</pre> ) : ( <div> loading... </div> )} {!!error && <div>{error.message}</div>} </div> ); }; export default Users;
123456789101112131415161718192021222324252627282930313233343536
Not using a queryClient will throw a runtime error
Error:No QueryClient set, use QueryClientProvider to set one
Should you use react-query
for production grade apps?
react-query
should definitely be on the top of the list when considering which library to use for server state management in react.
Conclusion #
We learned how to call rest api by using hooks in react. We started from the very basics, we considered our options and use libraries as we progressed to give us more features for handling server state. We didn't tackle too much on libraries like swr and react-query since it's outside the scope of this tutorial and I believe they deserver their own post.
I wanted to write this tutorial for beginners and I wanted to give a glimpse on what's being used in production just to broaded the scope of your knowledge.
Thank you for reading! Hopefully you enjoyed this tutorial. If you enjoyed, kindly leave a like or even share. If you like tutorials like these, please subscribe to our newsletter down below!
Resources #
usehooks-ts docs - https://usehooks-ts.com/react-hook/use-fetch
swr docs - https://swr.vercel.app/docs/getting-started
react-query docs - https://tanstack.com/query/v4/docs/overview
Credits: Image by Jan Alexander from Pixabay