While using a regular useEffect to run when a component is loaded to fetch data is super easy, it may result in unnecesary duplicate requests for data or unexpected errors when unmounting components. It is best to use a library that can provide hooks for fetching data, as not only does it solve the above issues, but also comes with useful features such as caching, background updates, and pre-fetching.
Below is an example of a standard data fetch in React:
const Component = () => {const [data, setData] = useState({});const [loading, setLoading] = useState(true);useEffect(() => {fetch("https://jsonplaceholder.typicode.com/todos/1").then(res => res.json()).then(json => {setData(json);setLoading(false);})}, [])return ({loading? <> {/* Display data here */} </>: <p>Loading...</p>})}
❌Figure: The traditional way of fetching data in React
This example is not ideal, as it means every time we reload this page component, or if we make the same request on another page, there will be an unnecessary request made instead of pulling the data from a cache.
Below are the two recommended options that both serve effectively the same purpose in providing developers with useful hooks for fetching data. These libraries not only give developers a wide range of other features, but also reduces the amount of boilerplate code they have to write.
TanStack Query is a feature-rich data fetching library developed by Tanstack. It can be used with existing data fetching libraries such as Axios, GraphQL packages such as graphql-request, or just plain fetch.
Here's a basic example of how you can use Tanstack Query:
import {useQuery,QueryClient,QueryClientProvider,} from "react-query";const queryClient = new QueryClient();function useTodos() {return useQuery("todos", async () => {const res = await fetch("/api/todos");const json = await res.json();return json;})}export const Page = () => {const { status, data, error, isFetching } = useTodos();if (status === "error") return <div>Error loading data: {error}</div>if (status === "loading") return <div>Loading...</div>return (<QueryClientProvider client={queryClient}><div><div>{/* Display todos here */}</div>{isFetching && <p>Re-fetching data in the background...</p>}</div></QueryClientProvider>}
This code employs the useQuery hook for asynchronous data fetching and a QueryClientProvider to manage the query cache in the component tree.
Some features of Tanstack Query:
useQueryisFetching valueretry and retryDelay options in useQuery, allowing you to specify the number of retries before giving updata.hasMore valueprefetchQuery{ queries: { suspense: true }} option added to QueryClientYou can find out more about Tanstack Query at tanstack.com/query.
SWR is an alternative to Tanstack Query developed by Vercel, the team behind Next.js. Much like Tanstack Query, SWR is library-agnostic, meaning you can use whatever data fetching library you are comfortable with.
Here's a basic example of how you can use the library's fetching hook:
const fetcher = (url) => fetch(url).then((res) => res.json());export const Page = () => {const { data, error, isLoading } = useSWR("/api/todos", fetcher);if (error) return <div>Error loading data</div>;if (loading) return <div>Loading...</div>;return <div>{/* Display todos here */}</div>;};
Some features of SWR:
useSWRInfinite hook{ suspense: true } optionuseSWRSubscription hookNote: Currently, the vast majority of SWR APIs are not compatible with the App router in Next.js 13.
You can find out more about using SWR at swr.vercel.app.
Additionally, RTK Query, part of the Redux Toolkit, is a similar library to SWR and React Query with tight integration with Redux and seamless type-safe importing sourced from OpenAPI specifications.
Here's a basic example of how you can use RTK Query:
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";const todosApi = createApi({baseQuery: fetchBaseQuery({ baseUrl: "/api" }),endpoints: (builder) => ({getTodos: builder.query<Array<Todo>, void>({query: () => "todos",}),}),});const { useGetTodosQuery } = todosApi;// For use with Reduxconst todosApiReducer = todosApi.reducer;const TodoPage = () => {const { data, isError, isLoading } = useGetTodosQuery();if (isLoading) return <p>Loading...</p>;if (isError) return <p>Error fetching todos</p>;return <div>{/*( Display todos here */}</div>;};
Some features of RTK Query:
Discover more about RTK Query in Redux Toolkit's official documentation at redux-toolkit.js.org/rtk-query/overview.