#How to call a route handler from a client component? (App Router)

38 messages ยท Page 1 of 1 (latest)

clever walrus
#

`// app/api/hello/route.js

import { NextResponse } from 'next/server';

export async function GET() {
return NextResponse.json({ message: 'Hello, world!' });
}

// components/ClientComponent.js

'use client'; // Use this directive to mark this as a client component

import React, { useEffect, useState } from 'react';

const ClientComponent = () => {
const [data, setData] = useState(null);

useEffect(() => {
const fetchData = async () => {
try {
const response = await fetch('/api/hello');
const result = await response.json();
setData(result);
} catch (error) {
console.error('Error fetching data:', error);
}
};

fetchData();

}, []);

return (
<div>
{data ? <p>{data.message}</p> : <p>Loading...</p>}
</div>
);
};

export default ClientComponent;

// app/page.js

import ClientComponent from '../components/ClientComponent';

export default function Home() {
return (
<div>
<h1>Welcome to Next.js 14 with the App Router!</h1>
<ClientComponent />
</div>
);
}`

I checked https://nextjs.org/docs/app/building-your-application/routing/route-handlers but there are 0 examples on how to call a route handler from a client component.

Can someone please share some best practices? Is using useEffect, and calling the route handler from there, the only way in Next.js 14?

Create custom request handlers for a given route using the Web's Request and Response APIs.

lone spearBOT
#

๐Ÿ”Ž This post has been indexed in our web forum and will be seen by search engines so other users can find it outside Discord

๐Ÿ•ต๏ธ Your user profile is private by default and won't be visible to users outside Discord, if you want to be visible in the web forum you can add the "Public Profile" role in id:customize

โœ… You can mark a message as the answer for your post with Right click -> Apps -> Mark Solution
(if you don't see the option, try refreshing Discord with Ctrl + R)

vestal jackal
#

useEffect can be looked down upon compared to swr/react query because it can fetch more then needed (ie dev mode strict react does useEffect twice),

but id ask you if you really need that and if prerender the data could be better

clever walrus
#

i cant prerender/fetch the data in a server component because, what is being fetched is dynamic and the fetch will be triggered on an onClick event in a client component. Or what did you have in mind exactly?

vestal jackal
#

yeah that makes sense, i was just thinking lots put their own "fancy" api when you can just use server component instead... for you i can see the value here (button doesnt need useEffect tho)

#

i personally use RSC fetch and SWR when i needed route hadler and client

clever walrus
#

I ended up with the following solution; seems to be working and I think the fetchCup in route.ts is cached by default?

#

`import { fetchCup } from "@/components/cups/requests/requests";
import { NextRequest } from "next/server";

export async function GET(request: NextRequest) {
const searchParams = request.nextUrl.searchParams;
const slugsQuery = searchParams.get("slugs");
const slugs = slugsQuery ? slugsQuery.split(",") : [];
const cups = await Promise.all(slugs.map((slug) => fetchCup(slug)));
const validCups = cups.filter((cup) => cup !== undefined);
return Response.json(validCups);
}`

#

and from my client component I call the route handler endpoint:

useEffect(() => { const fetchCupsData = async () => { const slugs = selectedCups.map((cup) => cup.slug); const queryParams = new URLSearchParams({ slugs: slugs.join(","), }); const response = await fetch(/api/cups?${queryParams.toString()}`);

  if (!response.ok) {
    throw new Error(
      `Failed to fetch cups. Status: ${response.status} ${response.statusText}`,
    );
  }

  const result = await response.json();

  if (!result || !Array.isArray(result)) {
    throw new Error("Unexpected response format.");
  }

  setCupsData(result);
};

fetchCupsData();

}, [selectedCups]);`

barren plover
#

Hi,

You can add code using three backlit ```

I highly recommend you to move from fetch in useEffect from something like react-query or swr.

I've made three fetch pattern example yesterday here: https://codesandbox.io/p/devbox/wispy-butterfly-dcddqw

To find the fetch pattern:

  • folder p1 : server only pattern
  • folder p2: server start fetching, client component resolve the fetch
  • folder p3: route handler and fetch from the client
#

For example, your current code under a react-query example:

function fetchCupsData(slugs) {
  const queryParams = new URLSearchParams({
    slugs: slugs.join(","),
  });
  const response = await fetch(/api/cups?${queryParams});

  if (!response.ok) {
    throw new Error(
    `Failed to fetch cups. Status: ${response.status} ${response.statusText}`,
    );
      }

  const result = await response.json();
  
  if (!result || !Array.isArray(result)) {
    throw new Error("Unexpected response format.");
  }
  return result;
}

function MyComponent() {
  const {data, isLoading} = useQuery({
    queryKey: ['cups', slugs] // i don't know where slugs came from
    queryFn: () => fetchCupsData(slugs)
  });
}
clever walrus
#

what would be the benefit of using react-query or SWR instead of fetch in a route handler? According to the docs route handlers are cached by default.

From the next.js docs:

Fetching Data on the Client with third-party libraries
You can also fetch data on the client using a third-party library such as SWR or TanStack Query. These libraries provide their own APIs for memoizing requests, caching, revalidating, and mutating data.

I do not need revalidating, mutating data.

I must admit I do not like useEffect but in this particular case is gets the job done and it's not that difficult to understand what it does.

But again, I'm open to suggestions/better/simpler solutions.

One cons is that I would need to learn another library and last time I played around with react-query I did not find the DX to be that good. SWR I've never used.

#

The code snippet you pasted does not look too complicated.

#

But I would be introducing more bytes to the client bundle with either of those libraries

#

That's also something to take into consideration

#

TBH what bugs me the most is that I have to use useEffect ๐Ÿ˜„

#

And actually now I'm a bit confused....

From the docs here:
https://nextjs.org/docs/app/building-your-application/routing/route-handlers

Route Handlers are cached by default when using the GET method with the Response object.

And then from the docs here:
https://nextjs.org/docs/app/building-your-application/data-fetching/fetching-caching-and-revalidating

In Route handlers, fetch requests are not memoized as Route Handlers are not part of the React component tree.

Create custom request handlers for a given route using the Web's Request and Response APIs.

Learn how to fetch, cache, and revalidate data in your Next.js application.

barren plover
#

Well, I would argue you are doing a worse cache of your request anyway using useState ๐Ÿ˜„

Without a joke, in the current useEffect, you have a few bugs / ux inconsistencies

  • no loading state
  • no error state
  • race condition if the component re-render (longer fetch response from server win)
  • no dedup of request if the component is render multiple times
clever walrus
#

so maybe it makes sense to use either TanStack Query / SWR after all? ๐Ÿ˜„

barren plover
#

The useEffect should look more like:

import { flushSync } from 'react-dom';

export function useQuery() {
  const [isLoading, setIsloading] = useState(false);
  const [data, setData] = useState();
  const [error, setError] = useState(null);

  useEffect(() => {
    let ignore = false;

    const fetchCupsData = async () => {
      const slugs = selectedCups.map(cup => cup.slug);
      const queryParams = new URLSearchParams({
        slugs: slugs.join(',')
      });

      const response = await fetch(`/api/cups?${queryParams}`);

      if (!response.ok) {
        throw new Error(`Failed to fetch cups. Status: ${response.status} ${response.statusText}`);
      }

      const result = await response.json();

      if (!result || !Array.isArray(result)) {
        throw new Error('Unexpected response format.');
      }

      if (!ignore) {
        setCupsData(result);
        setError(null);
      }
    };

    // flushSync to show user a feedback
    flushSync(() => setIsloading(true));
    fetchCupsData()
      .catch(err => {
        // do I want to clean data?
        // how do I get previousData?
        setError(err);
      })
      .finally(() => {
        setIsloading(false);
      });

    return () => {
      ignore = true;
    };
  }, [selectedCups]);

  return { isLoading, data, error };
}
#

And i'm not handling deduplication, previousData, pagination, etc

clever walrus
#

Thanks for pointing out all the flaws in my code. I appreciate it!

#

Let me start with no error state;

Here I'm thinking; let next.js handle this using error.tsx

barren plover
#

Well, looking at mine, there is still quite a few mistakes honestly ๐Ÿ˜… handling promise correctly really not that easy

clever walrus
#

there's a lot of what i would call boilerplate code in your example; that's one of the reasons I created this question in the first place... thinking that maybe nextjs has something clever to simplify all of this... like with const data = await fetch() in a server component. Easy (or easier/less code). With a client component... a totally different story... lots of code for one fetch ๐Ÿ˜ฆ

barren plover
#

The response needs to be from the React core team (there are unstable proposal like a use(promise))

The default is clearly react-query / swr for now until we have something baked in react

clever walrus
#

SWR seems more popular

barren plover
clever walrus
#

ah, checking...

#

hm they're both soaring like crazy

barren plover
#

Yeah, crazy popular solutions right now

clever walrus
#

which one has better docs in your opinion?

barren plover
#

Well, I've never used swr in prod, so my opinion is probably biased here

Both will be great, check both docs and usage of useQuery and settle on what clicks for you

clever walrus
#

I really liked the react query devtool extension but I went with SWR ๐Ÿ‘

vestal jackal
#

Swr has one too ๐Ÿ‘€

clever walrus
#

@vestal jackal yep, got it installed. It's not as fancy as TanStack Query's but it's still very useful.