Tips for sorting server components using a URL search parameter [Next.js, Prisma]

My goal is straightforward: I want to be able to filter my Prisma database based on parameters passed in the URL when I click on a CategoryBox.

After clicking on a CategoryBox, my URL does change to something like

http://localhost:3000/?category=XYZ
, but my list does not update to show objects in the category of XYZ.

The problem seems to lie with SearchParams being undefined in page.tsx.

File src/app/page.tsx

// Main home page (index)
import ClientOnly from "@/app/components/ClientOnly";
import Container from "@/app/components/Container";
import EmptyState from "@/app/components/EmptyState";
import getListings, { IListingsParams } from "@/app/actions/getListings";
import ListingCard from "@/app/components/listings/ListingCard";
import getCurrentUser from "@/app/actions/getCurrentUser";

interface HomeProps {
  SearchParams: IListingsParams;
}

const Home = async ({ SearchParams }: HomeProps) => {
  const listings = await getListings(SearchParams);
  const currentUser = await getCurrentUser();

  if (listings.length === 0) {
    return (
      <ClientOnly>
        <EmptyState showReset />
      </ClientOnly>
    );
  }

  return (
    <ClientOnly>
      <Container>
        <>
          <div
            className="grid">
            {listings.map((listing: any) => {
              return (
                <ListingCard
                  currentUser={currentUser}
                  key={listing.id}
                  data={listing}
                  imageSrcList={listing.imageSrc}
                />
              );
            })}
          </div>
        </>
      </Container>
    </ClientOnly>
  );
};

export default Home;

File src/app/components/navbar/Categories.tsx

"use client";

import CategoryBox from "../CategoryBox";
import Container from "../Container";
import { usePathname, useSearchParams } from "next/navigation";
import { GiFactory } from "react-icons/gi";

type CategoryType = "A" | "B";

interface CategoriesProps {
  selectedOption: CategoryType;
  visible: boolean;
}

export const categories = {
  A: [
    {
      label: "XYZ",
      icon: GiFactory,
      description: "...",
    },
  ],
  B: [
    {
      label: "ABC",
      icon: GiFactory,
      description: "...",
    },
  ],
};

const Categories: React.FC<CategoriesProps> = ({ selectedOption, visible }) => {
  const params = useSearchParams();
  const category = params?.get("category");
  const pathname = usePathname();
  const isMainPage = pathname === "/";

  if (!isMainPage || !visible) {
    return null;
  }

  return (
    <Container>
      <div className="pt-4 flex flex-row items-center justify-between overflow-x-auto">
        {categories[selectedOption].map((item) => (
          <CategoryBox
            key={item.label}
            label={item.label}
            icon={item.icon}
            selected={category === item.label}
          />
        ))}
      </div>
    </Container>
  );
};

export default Categories;

File src/app/components/CategoryBox.tsx

"use client";

import qs from "query-string";
import { useRouter, useSearchParams } from "next/navigation";
import { useCallback } from "react";
import { IconType } from "react-icons";

interface CategoryBoxProps {
  icon: IconType;
  label: string;
  selected?: boolean;
}

const CategoryBox: React.FC<CategoryBoxProps> = ({
  icon: Icon,
  label,
  selected,
}) => {
  const router = useRouter();
  const params = useSearchParams();

  const handleClick = useCallback(() => {
    let currentQuery = {};

    console.log("params", params);

    if (params) {
      currentQuery = qs.parse(params.toString());
    }

    const updatedQuery: any = {
      ...currentQuery,
      category: label,
    };

    if (params?.get("category") === label) {
      delete updatedQuery.category;
    }

    const url = qs.stringifyUrl(
      {
        url: "/",
        query: updatedQuery,
      },
      { skipNull: true }
    );

    router.push(url);
  }, [label, router, params]);

  return (
    <div
      onClick={handleClick}
      className={`
        flex
        flex-col
        items-center
        justify-center
        gap-2
        p-3
        border-b-2
        hover:text-neutral-800
        transition
        cursor-pointer
        ${selected ? "border-b-neutral-800" : "border-transparent"}
        ${selected ? "text-neutral-800" : "text-neutral-500"}
      `}>
      <Icon size={26} />
      <div className="font-medium text-sm">{label}</div>
    </div>
  );
};

export default CategoryBox;

File src/app/actions/getListings.ts

import prisma from "@/app/libs/prismadb";

export interface IListingsParams {
  userId?: string | undefined | null;
  category?: string;
}

export default async function getListings(params: IListingsParams) {
  try {
    const {
      userId,
      category,
    } = params || {};

    let query: any = {};

    if (userId) {
      query.userId = userId;
    }

    if (category) {
      query.category = category;
    }

    const listings = await prisma.listing.findMany({
      where: query,
      orderBy: {
        createdAt: "desc",
      },
    });

    const safeListings = listings.map((listing) => ({
      ...listing,
      createdAt: listing.createdAt.toISOString(),
    }));

    return safeListings;
  } catch (error: any) {
    throw new Error(error);
  }
}

I am currently following a video tutorial to learn more about this, although I believe I may have missed a crucial step or misunderstood some part.

To further clarify, the GitHub repository for the tutorial I am following can be found here: https://github.com/AntonioErdeljac/next13-airbnb-clone/tree/master

This project marks my initial attempt at a CRUD application using next.js and it has presented some challenges that I am working through.

Answer №1

It appears there is a casing error in your code; the proper prop name should be searchParams. Update your Home component as shown below and give it another try

const Home = async ({ searchParams }: HomeProps) => {
  const listings = await getListings(searchParams);
  const currentUser = await getCurrentUser();

  // make sure the search params are being passed correctly
  console.log(searchParams);
  
  // ... continue with the rest of your code 
  }
export default Home;

I also checked out the video tutorial you mentioned, and the instructor uses the correct prop name in the video - serachParams

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Retrieving data from MongoDB and presenting it neatly in Bootstrap cards

I have successfully retrieved data from MongoDB and displayed it in Bootstrap 5 cards. However, I am facing an issue where all the cards are appearing in a single row if there are multiple entries in the database. What I want to achieve is to utilize the ...

Retrieve the value of the Observable when it is true, or else display a message

In one of my templates, I have the following code snippet: <app-name val="{{ (observable$ | async)?.field > 0 || "No field" }}" The goal here is to retrieve the value of the property "field" from the Observable only if it is grea ...

What is the best way to pass an array as a parameter in Angular?

I have set up my routing module like this: const routes: Routes = [ { path: "engineering/:branches", component: BranchesTabsComponent }, { path: "humanities/:branches", component: BranchesTabsComponent }, ]; In the main-contin ...

How can I pass an array of string inputs into Vue 3?

Working with Vue 3, I'm setting up a form that will display text input fields corresponding to a fixed-length array of strings. Each field's v-model should connect to the respective string in the array. Here is my current code snippet for the vie ...

Experiencing ERR_TOO_MANY_REDIRECTS while using Next.js with Vercel and a Custom Domain through Cloudflare

I'm having trouble getting my Next.js project set up on Vercel with a custom domain managed through Cloudflare. Despite configuring the DNS and SSL settings correctly, I keep seeing a net::ERR_TOO_MANY_REDIRECTS error when trying to access my site at ...

What is the proper way to bring in Typescript types from the ebay-api third-party library?

My TypeScript code looks like this: import type { CoreItem } from 'ebay-api'; declare interface Props { item: CoreItem; } export default function Item({ item }: Props) { // <snip> } However, I encounter an issue when trying to build ...

Having trouble toggling webcam video in React/NextJS using useRef?

I have created a Webcam component to be used in multiple areas of my codebase, but it only displays on load. I am trying to implement a toggle feature to turn it on and off, however, I am facing difficulties making it work. Below is the TypeScript impleme ...

Utilize a combination of generic parameters as the keys for objects in TypeScript

Is there a method to utilize multiple generic parameters as object keys in TypeScript? I came across this answer which works well when there is only one parameter, but encounters issues with more than one. The error message "A mapped type may not declar ...

Try fetching new data with React Query by refetching

Whenever a button is clicked, I attempt to fetch new data by calling Refetch. const {refetch,data,isLoading} = useQuery( "getkurs",() =>fetch( `https://free.currconv.com/api/v7/convert? q=${selected.country.currencyId}_IDR&compa ...

Having trouble running npx create-react-app on node v20.9.0 - it's not cooperating!

When creating React apps, I used to use npx create-react-app. However, recently I started receiving the following warning message: (node:6372) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 close listeners added to [TLSSocket] ...

How can I implement the useState hook in server side rendering with Next.js?

Seeking a way to pass a value from the client side to the server side has been challenging. While useState is not available for use on the server side, there must be an alternative solution. The goal is to fetch a value from the client and incorporate it ...

Tips for utilizing localStorage within server components in the NextJS 13 app directory

Currently, I am utilizing an API call within the Metadata to retrieve information based on the inputted URL. The next/headers are used to access the URL. export async function generateMetadata( { params, searchParams }: MetadataProps, parent: Resolving ...

Encountering failures while running Angular tests in GitHub Actions due to reading inner text (which works fine locally)

I am facing an issue in my GitHub actions workflow where Karma is unable to read the 'innerText' of a native element for an Angular project. The error 'TypeError: Cannot read properties of null (reading 'innerText')' is being ...

Exploring the incorporation of interfaces into Vue.js/Typescript for variables. Tips?

I have an interface:   export interface TaskInterface{ name: string description1: string time: string } and a component import { TaskInterface } from '@/types/task.interface' data () { return { tasks: [ { name: 'Create ...

Error: Unable to perform operation on undefined object when trying to map over 'reminder' object

I've been struggling with my reminder-list.tsx file. No matter how many times I try to fix it, I always end up failing. Can someone help me figure out how to resolve this issue? Every time I run the code, I get the TypeError: undefined is not an obje ...

Utilize clipboard functionality in automated tests while using Selenium WebDriver in conjunction with JavaScript

How can I allow clipboard permission popups in automated tests using Selenium web driver, Javascript, and grunt? https://i.stack.imgur.com/rvIag.png The --enable-clipboard and --enable-clipboard-features arguments in the code below do not seem to have an ...

Steps to avoid HTML encoding the ' character in Next.js _document.js file

Currently, I am working on integrating VWO into my website by following the steps mentioned here. I have included the VWO script tag within a NextJs Head tag as shown below: <Head> <script type='text/javascript' id='vwoC ...

To initiate the development environment, execute the following command: `cross-env NODE_ENV=

[email protected] start /Users/ssurisettii/Documents/il-17g5-app cross-env NODE_ENV=development npm run webpack-development sh: cross-env: command not found npm ERR! code ELIFECYCLE npm ERR! syscall spawn npm ERR! file sh npm ERR! errno ENOENT npm ER ...

What is the best way to design functions that can return a combination of explicit types and implicit types?

When looking at the code provided below, function system(): ISavable & ISerializable { return { num: 1, // error! save() {}, load() {}, serialize() {}, deserialize() {}, } } interface ISavable { sa ...

How can you merge arrays in Angular based on their unique names?

Is there a way to combine objects within the same array in Angular based on their names? [{ "name":"Navin", "id":"1" }, { "name":"Navin", "mark1":"98" ...