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

Dynamic Type in Typescript Record

Looking for a way to attach types to record names in a class that returns a Record. The current code snippet is as follows: interface DataInterface { bar: number; foo: string; fooBar: boolean; } export class MyClass { public bar: number; p ...

Exploring the Wonders of React Memo

I recently started delving into the world of React. One interesting observation I've made is that when interacting with componentized buttons, clicking on one button triggers a re-render of all button components, as well as the parent component. impo ...

Issue with retrieving properties in Angular template due to undefined values in HTML

As a newbie to Angular, I am dedicated to improving my skills in the framework. In my project, I am utilizing three essential files: a service (Studentservice.ts) that emits an observable through the ShowBeerDetails method, and then I subscribe to this ob ...

Importing Typescript modules by specifying their namespace instead of using a function

I have been working on a project where I needed to generate typings from graphql using the gql2ts library. In the gql-2-ts file, I initially used a namespace import for glob, which resulted in TypeScript showing me an error as intended. I then switched the ...

Angular: Initiate multiple functions simultaneously and combine results afterwards

My current code successfully zips and saves the response of a JSON array by splitting them into individual files using a single method. zip: JSZip = new JSZip(); folder: JSZip = new JSZip(); this.apicall.api1() .subscribe( response => { for (let r ...

Nestjs opts to handle invalid routes by throwing a NotFoundException rather than a MethodNotAllowed

I've recently developed a Rest API using NestJS and now I'm focusing on customizing error responses. Specifically, I want to address the scenario where a user calls an endpoint with the incorrect HTTP method. Take for instance the endpoint GET / ...

Breaking down arrays in Typescript

Let's say I have an array like this: public taskListCustom: any=[ {title: 'Task 1', status: 'done'}, {title: 'Task 2', status: 'done'}, {title: 'Task 3', status: 'done'}, {title: 'Task ...

Prop type error: The `href` prop within the `<Link>` component should be a `string` or `object`, but it was `undefined`

import BlogData from "./BlogData"; import Link from 'next/link' function Card(props) { return ( <> <div className="post-content"> <div className="post-image"&g ...

An issue occurred while attempting to retrieve information from the database table

'// Encounter: Unable to retrieve data from the table. // My Code const sql = require('mssql/msnodesqlv8'); const poolPromise = new sql.ConnectionPool({ driver: 'msnodesqlv8', server: "test.database.windows.net", ...

The Child/Parent arguments in Typescript methods cannot be assigned

Why is this not working in TypeScript? class Parent { id: string = '' } class Child extends Parent{ name: string = '' } const fails: (created: Parent) => void = (created: Child) => { return }; const failsToo: ({ create ...

Create Angular file structures effortlessly using a tool similar to Rails scaffold

Is there a code generator in Angular similar to RoR's rails scaffold? I am looking to run a specific command and receive the following files, such as: *.component.html *.component.sass *.component.ts *.module.ts. ...

Loading AngularJS multiple times

I'm facing a challenge while upgrading my angularJs Application to Webpack4. This is how I've set it up: vendor.ts import "angular"; import "angular-i18n/de-de"; import "angular-route"; and main.ts import {MyAppModule} from "./my-app.app"; ...

Developing a discriminated union by utilizing the attribute names from a different type

In my quest to create a unique generic type, I am experimenting with extracting property names and types from a given type to create a discriminated union type. Take for example: type FooBar = { foo: string; bar: number; }; This would translate t ...

Assign the element to either interface A or interface B as its type

Is there a way to assign a variable to be of type interface A OR interface B? interface A { foo: string } interface B { bar: string } const myVar: A | B = {bar: 'value'} // Error - property 'foo' is missing in type '{ bar: s ...

Solving the error message "Cannot find module '@angular/router/src/utils/collection' or its corresponding type declaration"

How do I troubleshoot this Error: src/app/metronic/orderByLocation/locationsByOneOrder/locationsByOneOrder.component.ts:7:25 - error TS2307: Cannot find module '@angular/router/src/utils/collection' or its corresponding type declarations.m 7 imp ...

The issue of the fixed navbar overlapping the scrollbar of the page occurs when utilizing the overflow-y scroll feature

I am trying to create a web page with snap scroll and a fixed navbar that remains at the top. However, I'm facing an issue where the navbar is overlapping the right scroll bar, which should not be happening. If I remove the overflow-y property from th ...

Angular version 7.2.1 encounters an ES6 class ReferenceError when attempting to access 'X' before it has been initialized

I have encountered an issue with my TypeScript class: export class Vehicule extends TrackableEntity { vehiculeId: number; constructor() { super(); return super.proxify(this); } } The target for my TypeScript in tsconfig.json is set to es6: ...

Creating a Vue 3 Typescript project may lead to encountering the error message "this is undefined"

Just diving into Vue using Vite and TypeScript for my project, but running into errors during the build process. Most of them are Object is possibly 'undefined', particularly in parts of my template like this: <input :value="this.$store.s ...

Unable to connect information to list item

I'm struggling to figure out why I am unable to bind this data into the li element. When I check the console, I can see the data under calendar.Days and within that are the individual day values. Any assistance would be highly appreciated. Code @Comp ...

Can you please explain how I can retrieve information from a Firebase collection using the NextJs API, Firebase Firestore, axios, and TypeScript?

I'm currently utilizing api routes within NextJS 13 to retrieve data from Firebase. The code for this can be found in api/locations.tsx: import { db } from "../../firebase"; import { collection, getDocs } from "firebase/firestore"; ...