#Fetch data from server component based on state from client component

39 messages · Page 1 of 1 (latest)

mossy ravine
#

I’m trying to figure out the most appropriate fetching pattern for a multi-tenant prop tech app I’m building.

Goal

  • Something similar to the filters on Zillow’s property search page

Conditions

  • Next 13 with app router
  • Filters are set in state in a client component
  • Query params are built based off of the filter selections
  • Those query params are then appended to an external API endpoint that will then send me back results that match the filters.

Some gotchas

  • I’d like to avoid fetching in the client component if I can. In order to be authenticated I have to pass a private API key for the fetch. Seems maybe a bit insecure to pass that into a client component. These api key must exist in my db and can’t be env vars. Based on this fact, I have not tried the SWR/React Query way to fetch inside the client component.
  • I figured my next option would be to ditch the local state and create context for this. Got it working (took a couple hours) only to find out that I can’t seem to access context in a sever component:

Cannot access ._context on the server. You cannot dot into a client module from a server component. You can only pass the imported name through.

**Thoughts **

  • Some have suggested to just try to do a big fetch of all of the data and just do the actual filtering client side. I get it. I can see the pros. There are drawbacks too. I’ve had a few discussions with the creator of the external API I’m using and he highly recommended using their filter. A lot of his clients assume they will get a performance increase by doing a big fetch then filtering client side but he’s adamant that there is zero performance increase. I would like to at least try using their filter before recreating parts of it myself.

I’m a bit stumped here!

Happy to provide code but might just be worth talking high level first.

amber gale
#

A good approach would be to, instead of using client state like an useState for the filters

#

Use url query params. For example, when the user filters a type, add to the url the corresponding query string. Whenever that happens, Next automatically refetches the page and surprisingly it works. You can work from there and basically implement it as you want

mossy ravine
#

Thanks for this! I ended up implementing a solution using server actions that I’m pretty happy with. But I do like the search params approach too! I had attempted something like this but was unsuccessful. I’ll have a look at what you posted here. I still think having search params in the URL is ultimately something I want. You get a lot of utility out of it.

amber gale
#

Yep, also shareable url state and nextjs seems to handle this pretty well, even with automatic caching

mossy ravine
#

Gonna try to refactor for this. Just seems like a better solution. Server Actions just aren't ready for prime time. The way I'm using it presents a bit of a sec risk is that I have some API keys in my server function.

amber gale
#

yeah, if it is a serious app dont use them, they are experimental

#

But afaik what I used in the repo is entirely production ready according to NextJS so you should be fine

mossy ravine
#

Awesome. What do you think would be the best pattern to account for pagination with this approach?

amber gale
#

Probably something similar to what I did with the categories? Have a page state in the url(with default to 1) and from there, add buttons to do router.replace with your own logic and that's it

#

The only pitfall this has is that the whole page gets 'refetched' when any url state changes

#

In most cases it is fine, since if you change a parameter or filter you'd stil want to refetch all products in this example

#

I still think it is a good pattern as long as your use case is fine with that

mossy ravine
#

My use case is basically building a bit of a Zillow clone. I'm fetching a ton of properties from an external API vendor.

amber gale
#

tbh just looked at their page and seems suitable

#

Like, when you change one filter you'll want everything to be recomputed

mossy ravine
#

indeed. the one thing I find curious is that the map has all of the results, whereas the list of cards gets paginated

#

a different problem though

amber gale
#

I have no tested this with production apps so there might be pitfalls I am not aware of

mossy ravine
#

I'll give it a shot and see how it goes!

amber gale
#

The other approach if you cant is just do client component, and store your api key on the server(do not send it to the client). Then, create a api route handler that takes inputs, calls the external api with your key, and returns the external api results ( basically a wrapper, afaik this would be the most common approach)

mossy ravine
#

Yes totally. Making an api route (router handler?) would be totally viable as well.

mossy ravine
#

@amber gale For some reason after I update the route with the params, my searchParams prop is always an empty object. I must be missing something.

mossy ravine
#
  • I'm able to set my searchParams just fine with router.replace
  • I can see it update in the URL
  • whenever I log out my searchParams prop in my page.tsx it's always an empty obj.
export default async function Page({ params, searchParams }: SearchProps) { 
  const searchParamString = searchParams.toString();

  // Get site data
  const { site } = params;
  const data = await getSiteData(site);

  // Get filtered listings data
  const filteredListingsData = getFilteredListingsData(
    data.user?.repliersKey,
    searchParamString
  );

console.log(searchParams);  // this is always empty
amber gale
#

Hmm which next version are you using? @mossy ravine

#

Code seems fine

mossy ravine
#

Tried a few versions of 13.4

#

13.4.1, 13.4.4 and 13.4.5-canary.6

mossy ravine
#

Does it matter exactly how the search params are set? I have a function that takes all of my state and makes a string of params then I use that in a useEffect on router.replace - like I said, it works fine. I can see the exact params I need in the URL. It's in a useEffect for now cause it was just quicker for me to test this way.

const params = createParamsFromState();

useEffect(() => {
  startTransition(() => {
    router.replace(`${pathname}?${params}`);
  });
}, [router, params, pathname]);
grand dust
amber gale
#

@grand dust np, glad I could help:)

mossy ravine
#

For some reason I could never get it to work. My searchParams props we're always an empty {}, even when useSearchParams() was able to get the params just fine. I think it's a Next.js bug. Either that or something happens when you do a middleware hostname rewrite that messes up the searchParams props

#

I ended up using a route handler with SWR

grand dust
amber gale
mossy ravine
#

@amber gale yeah I don't mind. At this point I'm so close to getting this SWR method to work. It works on dev but not on prod 😭

amber gale
#

Can you dm me? Also, you can take a look at react query, altough with many filters it might be messy(the good thing about the url thing is that next does the heavy lifting) Shipped a production app and it is very good