nahuel.luca()

Fetch data on Client-Side NextJS

Let's talk about how to fetch data in NextJS on the client side. We explore some definitions and different ways to do it,

Data fetching in NextJS

So, in first place take a look how NextJS recommend fetch data in App router. NextJS recommend whenever possible fetch data on the server wth sever components for this reasons:

  • Have direct access to backend data resources (e.g. databases).
  • Keep your application more secure by preventing sensitive information, such as access tokens and API keys, from being exposed to the client.
  • Fetch data and render in the same environment. This reduces both the back-and-forth communication between client and server, as well as the work on the main thread on the client.
  • Perform multiple data fetches with single round-trip instead of multiple individual requests on the client.
  • Reduce client-server waterfalls.
  • Depending on your region, data fetching can also happen closer to your data source, reducing latency and improving performance.

So now we know, we have to fetch data on the server when possible. But, if fetching data on the client is required, what do we do?

What ways we have to fetch data in client side?

Exits 3 ways to fetch data in client side, using patterns data fetching, Routes Handlers and third-party libraries

Route Handlers

We can define one Route Handler and then called in our client component, the Route Handler execute on the server and return the data to the client. For example:

Client component:
"use client";

import {useEffect, useState} from "react";

import CharactersCard from "@/components/characters-card";

export default function Characters() {
  const [data, setData] = useState([]);

  useEffect(() => {
    async function fetchData() {
      const response = await fetch("/api/characters");
      const data = await response.json();

      setData(data);
    }
    fetchData();
  }, []);

  return (
    <section className="space-y-10">
      sarasa
      <div className="flex justify-center flex-wrap gap-8">
        {data?.map((character: any) => (
          <CharactersCard
            key={character.id}
            image={character.image}
            location={character.location.name}
            name={character.name}
            species={character.species}
            status={character.status}
          />
        ))}
      </div>
    </section>
  );
}
Route Handler:
export async function GET() {
  const res = await fetch("https://rickandmortyapi.com/api/character");
  const data = await res.json();
  const result = await data.results;

  return Response.json(result);
}

Patterns data fetching

Its is simple, we have a React Server Component to fetch data and then pass that to a Client Component. Something like that:

export default async function ServerComponent() {
  const data = await getCharacters();

  return (
    <div>
      {/* client component */}
      <Characters characters={data} />
    </div>
  );
}

Using third-party libraries

We can use libraries such as SWR or TanStack Query, in this example we will use TanStack Query.

What is TanStack Query?

TanStack query (before called React Query) is a data-fetching library for web applications this library help us for cache data, revalidate data, mutations and other cools things.

So how fetch data on the client with TanStack Query?

First of all, after install TanStack Query we will set up a QueryProvider and and wrap our children.

// provider/query-provider.tsx
"use client";

import {QueryClient, QueryClientProvider} from "@tanstack/react-query";

function makeQueryClient() {
  return new QueryClient({
    defaultOptions: {
      queries: {
        staleTime: 60 * 1000,
      },
    },
  });
}

let browserQueryClient: QueryClient | undefined = undefined;

function getQueryClient() {
  if (typeof window === "undefined") {
    return makeQueryClient();
  } else {
    if (!browserQueryClient) browserQueryClient = makeQueryClient();

    return browserQueryClient;
  }
}

export default function QueryProviders({children}: {children: React.ReactNode}) {
  const queryClient = getQueryClient();

  return <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>;
}

Layout:

// app/layout.tsx
import QueryProviders from "@/providers/query-provider";

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en">
      <body>
        <div className="mt-32 mb-10 p-5">
          <QueryProviders>{children}</QueryProviders>
        </div>
      </body>
    </html>
  );
}

Now we configure the queryClient, for this we create a route, this route will be in charge of prefetching data.

// app/characters/page.tsx
import {dehydrate, HydrationBoundary, QueryClient} from "@tanstack/react-query";

import Characters from "./characters";
import {getCharacters} from "./queries";

export default async function page() {
  const queryClient = new QueryClient();

  await queryClient.prefetchQuery({
    queryKey: ["characters"],
    queryFn: getCharacters,
  });

  return (
    <HydrationBoundary state={dehydrate(queryClient)}>
      <Characters />
    </HydrationBoundary>
  );
}

All ready to fetch data, now we create a component where call the data

// app/characters/characters.tsx
"use client";

import {useQuery} from "@tanstack/react-query";

import CharactersCard from "@/components/characters-card";

import {getCharacters} from "./queries";

export default function Characters() {
  const {data} = useQuery({queryKey: ["characters"], queryFn: getCharacters});

  return (
    <section className="space-y-10">
      <div className="flex justify-center flex-wrap gap-8">
        {data?.map((character: any) => (
          <CharactersCard
            key={character.id}
            image={character.image}
            location={character.location.name}
            name={character.name}
            species={character.species}
            status={character.status}
          />
        ))}
      </div>
    </section>
  );
}

That's all in this post. Thank you very much for reading!

Below I will leave the resources I reviewed, you can also see an app using these deploy concepts, see the deploy app here and the code on github here.

Resources