estevan426
estevan426

Reputation: 323

update the query params of the page using a input in NextJS (App Router)

I'm attempting to dynamically update query parameters based on user input using the onChange event. However, I'm encountering an issue where, upon entering text, the query parameters are updated first, and only after a brief delay does the input value get updated. My goal is to achieve real-time synchronization between the input value and the query parameters, but the current implementation isn't achieving that.

Below is the relevant JavaScript code implemented in a Next.js 14 environment (App Router), utilizing the useSearchParams, usePathname, and useRouter hooks. I already put the 'use client' on top of the component.

const searchParams = useSearchParams();
const pathname = usePathname();
const router = useRouter();

const name = searchParams.get('name') || '';

const handleFilter = (e) => {
    const { name: key, value } = e.target;

    const params = new URLSearchParams(searchParams);

    params.set(key, value);
    params.set('page', 1);

    router.push(`${pathname}?${params.toString()}`);
};

<input
    type="text"
    className="form-control"
    placeholder="Name of the user"
    id="name"
    name="name"
    value={name}
    onChange={handleFilter}
/>

One solution is use the shallow routing, but I can't find any documentation of useRouter (from next/navigation) using the shallow equals true.

Upvotes: 4

Views: 11717

Answers (3)

Noah Pittman
Noah Pittman

Reputation: 13

What is working for me (for now) is using history.replaceState()!

There is a small issue on Safari & iOS where calling this over like a thousand times in 10 seconds will cause a client-side error. My app involves a color picker which stores the color in state, dragging around the color picker for a while causes this.

Other than that, it works perfect for me. For your usage, this should hit the spot just right.

Here's a fully working client component below with your code (Next14 App Router).

"use client";

import { usePathname, useRouter, useSearchParams } from "next/navigation";
import { useEffect, useState } from "react";

const Page = () => {
    // removed useRouter because it was unused and doesnt support shallow routing in App Router
    const searchParams = useSearchParams();
    const params = new URLSearchParams(searchParams);
    const pathname = usePathname();

    // Store name and url state in a variable
    const [name, setName] = useState<string>("");
    const [urlState, setUrlState] = useState<string>("");

    // params helper function
    const setParams = (key: string, value: string | number) => {
        params.set(key, value.toString());
    };

    // useEffect to set the name and page params on pageload and when the name from the imnput changes
    useEffect(() => {
        console.log(name);
        setParams("name", name);
        setParams("page", 1);
        history.replaceState(null, "", `${pathname}?${params.toString()}`);
    }, [name]);

    // modified handlefilter function to set the name state
    const handleFilter = (e: any) => {
        const { name: key, value } = e.target;
        setName(value);
    };

    // component
    return (
        <input
            type="text"
            className="form-control"
            placeholder="Name of the user"
            id="name"
            name="name"
            value={name}
            onChange={handleFilter}
        />
    );
};

export default Page;

Hopefully that works out for you! I wish they would implement a shallow routing alternative for App Router. Until then I'll caveman my way through life.

Upvotes: 0

Funmi Ayinde
Funmi Ayinde

Reputation: 382

You can check this out

import { usePathname, useSearchParams, useRouter } from "next/navigation";
import { useState, useEffect } from "react";

export default function Home() {
  const [searchQuery, setSearchQuery] = useState({
    name: "",
    // you can add more keys to this
  });
  const searchParams = useSearchParams();
  const pathname = usePathname();
  const router = useRouter();

  const handleInputChange = (event) => {
    const { name, value } = event.target;
    const updatedQuery = { ...searchQuery, [name]: value };
    setSearchQuery(updatedQuery);
    updateSearchQuery(updatedQuery);
  };

  const updateSearchQuery = (updatedQuery) => {
    const params = new URLSearchParams(searchParams);
    Object.keys(updatedQuery).forEach((key) => {
      if (updatedQuery[key]) {
        params.set(key, updatedQuery[key]);
      } else {
        params.delete(key);
      }
    });
    params.set('page', '1');
    const queryString = params.toString();
    const updatedPath = queryString ? `${pathname}?${queryString}` : pathname;
    router.push(updatedPath);
  };

  return (
    <main>
      <div>
        <input
          type="text"
          className="form-control"
          placeholder="Name of the user"
          id="name"
          name="name"
          value={searchQuery.name}
          onChange={handleInputChange}
        />
      </div>
    </main>
  );
}

Upvotes: 4

Hummel
Hummel

Reputation: 372

Make the input controlled by keeping the value in a state will keep it reactive.

In more detail:

  1. Keep a state with the search parameter.
  2. Add key down (or change) listener to update the state.
  3. update the search parameters in useEffect.
const [search, setSearch] = useState(value);
const searchParams = useSearchParams();
const pathname = usePathname();
const router = useRouter();

useEffect(() => {
    const params = new URLSearchParams(searchParams);
    params.set('name', search);
    params.set('page', 1);
    router.push(`${pathname}?${params.toString()}`);    
}, [search]

const changeHandler = (e) => {
    const { value } = e.target;
    setSearch(value);
}

<input
    type="text"
    className="form-control"
    placeholder="Name of the user"
    id="name"
    name="name"
    value={search}
    onkeydown={changeHandler }
/>

Upvotes: 1

Related Questions