How can I provide type annotations for search parameters in Next.js 13?

Within my Next.js 13 project, I've implemented a login form structure as outlined below:

"use client";

import * as React from "react";
import { zodResolver } from "@hookform/resolvers/zod";
import { signIn } from "next-auth/react";
import { useForm } from "react-hook-form";
import * as z from "zod";
import { useSearchParams } from "next/navigation";
import Link from "next/link";

import { cn } from "@/lib/util";
import { userAuthSchema } from "@/lib/validations/auth";
import { buttonVariants } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Icons } from "@/components/icons";
import { loginUser } from "@/lib/login-user";

export type FormData = z.infer<typeof userAuthSchema>;

export function LoginForm() {
  const {
    register,
    handleSubmit,
    reset,
    formState: { errors },
  } = useForm<FormData>({ resolver: zodResolver(userAuthSchema) });

  const [isLoading, setIsLoading] = React.useState<boolean>(false);
  const [error, setError] = React.useState<string | null>(null);
  const [isGoogleLoading, setIsGoogleLoading] = React.useState<boolean>(false);

  const searchParams = useSearchParams();

  async function onSubmit(submittedData: FormData) {
    await loginUser({
      submittedData,
      setError,
      setIsLoading,
      reset,
      searchParams,
    });
  }

  return (
    <div className="grid gap-6">
      {error && (
        <p className="text-sm text-red-500 animate-in fade-in-0 slide-in-from-left-1">
          {error}
        </p>
      )}
      <form onSubmit={handleSubmit(onSubmit)}>
        <div className="grid gap-6">
          <div className="grid gap-1">
            <Label htmlFor="email">Email</Label>
            <Input
              id="email"
              type="email"
              placeholder="<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="157b74787055706d74786579703b767a78">[email protected]</a>"
              autoCapitalize="none"
              autoComplete="email"
              autoCorrect="off"
              disabled={isLoading || isGoogleLoading}
              {...register("email")}
            />
            {errors.email && (
              <p className="px-1 text-xs text-red-600 animate-in fade-in-0 slide-in-from-left-1">
                {errors.email.message}
              </p>
            )}
          </div>
          <div className="grid gap-1">
            <div className="flex items-center justify-between">
              <Label htmlFor="password">Password</Label>
              <Link
                href="/request-password-reset"
                className="text-sm font-medium text-sky-700 hover:underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-sky-400 focus-visible:ring-offset-2 active:text-sky-400"
              >
                Forgot password?
              </Link>
            </div>
            <Input
              id="password"
              type="password"
              autoCapitalize="none"
              autoComplete="off"
              autoCorrect="off"
              disabled={isLoading || isGoogleLoading}
              {...register("password")}
            />
            {errors.password && (
              <p className="px-1 text-xs text-red-600 animate-in fade-in-0 slide-in-from-left-1">
                {errors.password.message}
              </p>
            )}
          </div>
          <button className={cn(buttonVariants())} disabled={isLoading}>
            {isLoading && (
              <Icons.spinner className="mr-2 h-4 w-4 animate-spin" />
            )}
            Login
          </button>
        </div>
      </form>
      <div className="relative">
        <div className="absolute inset-0 flex items-center">
          <span className="w-full border-t border-neutral-200" />
        </div>
        <div className="relative flex justify-center text-xs uppercase">
          <span className="bg-neutral-100 px-2">Or</span>
        </div>
      </div>
      <button
        type="button"
        className={cn(buttonVariants({ variant: "outline" }), "w-full")}
        onClick={() => {
          setIsGoogleLoading(true);
          signIn("google", {
            callbackUrl: searchParams?.get("from") || "/blog",
          });
        }}
        disabled={isLoading || isGoogleLoading}
      >
        {isGoogleLoading ? (
          <Icons.spinner className="mr-2 h-4 w-4 animate-spin" />
        ) : (
          <Icons.google className="mr-2 h-6 w-6" />
        )}{" "}
        Login with Google
      </button>
    </div>
  );
}

The usage of searchParams can be noticed in the loginUser() method. This is an essential argument passed within the onSubmit event handler. Below is the code snippet that demonstrates this functionality:

  async function onSubmit(submittedData: FormData) {
    await loginUser({
      submittedData,
      setError,
      setIsLoading,
      reset,
      searchParams,
    });
  }

Let's explore the loginUser() function:

import type { FormData } from "@/components/auth/register-form";
import { signIn } from "next-auth/react";

const RESPONSE_MESSAGES = {
  USER_NOT_FOUND: "User not found",
  INVALID_PASSWORD: "Invalid password",
  LOGIN_SUCCESSFUL: "Logged in successfully",
  LOGIN_FAILURE: "Login failed. Please try again.",
  EMAIL_NOT_VERIFIED: "Please verify your email",
};

interface LoginUserProps {
  submittedData: FormData;
  setError: React.Dispatch<React.SetStateAction<string | null>>;
  setIsLoading: React.Dispatch<React.SetStateAction<boolean>>;
  reset: () => void;
  searchParams: any;
}

export async function loginUser({
  submittedData,
  setError,
  setIsLoading,
  reset,
  searchParams,
}: LoginUserProps) {
  setIsLoading(true);
  setError(null);

  try {
    const response = await fetch("/api/login", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify(submittedData),
    });

    const responseData = await response.json();

    if (!response.ok) {
      throw new Error(RESPONSE_MESSAGES.LOGIN_FAILURE);
    }

    // Additional logic based on response messages

  } catch (error) {
    setError((error as Error).message);
  } finally {
    setIsLoading(false);
  }
}

In relation to the type declaration for searchParams, it is currently set as any. If you have insights on determining the exact data type, please contribute. Thanks!

Answer №1

When using the useSearchParms function, the return type is different depending on whether it is in the App directory or the pages directory. In the App directory, it returns ReadonlyURLSearchParams, while in the pages directory it returns ReadonlyURLSearchParams | null.

The ReadonlyURLSearchParams module is obtained from the next/navigation library.

import { ReadonlyURLSearchParams } from "next/navigation"
// ...
const searchParams = useSearchParams();
// ?^ searchParams: ReadonlyURLSearchParams 

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

Press the text in a React Native TypeScript component to trigger a render

I am a newcomer to React Native and TypeScript, and I am struggling to figure out how to display something on the page of my app after a button is pressed. Below is the function I'm using: const getRandomNumber = () ={ const number = Math.fl ...

How can you trigger a 'hashchange' event regardless of whether the hash is the same or different?

Having a challenge with my event listener setup: window.addEventListener('hashchange', () => setTimeout(() => this.handleHashChange(), 0)); Within the handleHashChange function, I implemented logic for scrolling to an on-page element whil ...

The error message "Cannot send headers after they have already been sent to the client" is caused by attempting to set headers multiple

Although I'm not a backend developer, I do have experience with express and NodeJS. However, my current project involving MongoDB has hit a roadblock that I can't seem to resolve. Despite researching similar questions and answers, none of the sol ...

How to convert an array of object values to an object with array values in a React application?

Here is an example of an array containing object values: { user: ["data1", "data2"], user2: ["data1", "data2"], } I am looking to convert this object value array into an object value array object, like so: { ...

Checking JavaScript files with TSLint

After spending many hours attempting to make this work, I still haven't had any success... I am wondering: How can I utilize TSLint for a .js file? The reason behind this is my effort to create the best possible IDE for developing numerous JavaScrip ...

Error TS2307 - Module 'lodash' not found during build process

Latest Update I must apologize for the confusion in my previous explanation of the issue. The errors I encountered were not during the compilation of the Angular application via Gulp, but rather in the Visual Studio 'Errors List'. Fortunately, I ...

Utilizing query parameters in Next.js

I've been working on a unique Next.js application that incorporates both infinite scroll and a search input feature. The infinite scroll functionality loads 6 additional items whenever the user reaches the bottom of the page. On the other hand, the s ...

Is it possible to use non-numeric values as keys in a Typescript Map?

During a lesson: private items: Map<number, string> = new Map(); ... this.items[aNumber] = "hello"; Results in this error message: An element has an any type because the expression of type number cannot be used to index type Map<numbe ...

Typescript tutorial: Implementing a 'lambda function call' for external method

The Issue Just recently diving into Typescript, I discovered that lambda functions are utilized to adjust the value of this. However, I find myself stuck on how to pass my view model's this into a function that calls another method that hasn't b ...

Looking to display parent and child elements from a JSON object using search functionality in JavaScript or Angular

I am trying to display both parent and child from a Nested JSON data structure. Below is a sample of the JSON data: [ { "name": "India", "children": [ { "name": "D ...

Upcoming 14: The page file remains unresponsive to any input

After upgrading to Next 14 from version 12, I encountered a problem with creating pages using the app directory. In my layout file, I defined a Navbar which functions correctly, however, the children of the layout file are not reacting as expected. The lin ...

Angular: Unable to locate route declaration in the specified path /src/app/app-routing.module.ts

Whenever I attempt to set up automatic routing for components that have been created using the command below ng generate module orders --route orders --module app.module I encounter the following error message The requested URL /src/app/app-routing.mod ...

Tips for using the fetch() method to send an object to a dynamic API route in Next.js

I'm running into a problem when trying to send an object to my dynamic API route in Next.js. Everything works fine when sending a regular string, and I can update my MongoDB without any issues. However, when attempting to send an object, the request d ...

Having trouble implementing the Material UI time picker because it does not meet the required DateTime format

REVISE I recently switched my dataType from DateTime to TimeSpan in my code. I have a functioning MVC version that already uses TimeSpan, and the times are posted in HH:MM format. Now, I am unsure if the issue lies with the headers set up on Axios or if it ...

Setting up proxy_pass with multiple parameters along with a dynamic route and invoking an API call

Having trouble setting up a reverse proxy for an axios request that includes a variable chainId to communicate with an external moralis API. Encountering a 502 Bad Gateway error. Server Route Setup /moralis/ERC20/${token.address}?chainId=${chainId} Code ...

The functionality of Layout.tsx is inconsistent across various pages

I'm having trouble with the console.log() code to display the page path only showing up in the "pages.tsx" file (src/app/pages.tsx) and not appearing in the console for other files located in (src/page/Login). Here is the code from layout.tsx: ' ...

In Next.js, the 404 error page is displayed using getInitialProps

Currently, I am learning how to redirect users in getInitialProps by following a helpful example. Here is the link to the example I am referring to However, I encountered an issue where when I try to return a 404 error using the code snippet provided, in ...

Setting the useState hook to a User type in React - the ultimate guide!

As someone new to hooks, I'm unsure about what the initial value for useState should be set to. Currently, an empty object is set as the default value for useState with const [user, setUser] = useState({}); This is causing ${crafter.id} to throw an e ...

There is no index signature that includes a parameter of type 'string' in the specified type

I have a background in mobile app development and am looking to learn more about TypeScript. How can I declare a map object with the form [string:any]? The error is occurring at line: map[key] = value; Element implicitly has an 'any' type becaus ...

Issue - Unrecognized listen EADDRINUSE :::5432 detected in Windows Command Prompt

I encountered an issue when I tried running gulp serve --nobrowser, resulting in the following error: { Error: listen EADDRINUSE :::5432 at Object._errnoException (util.js:992:11) at _exceptionWithHostPort (util.js:1014:20) at Server.setupListenHandle [as ...