How to Ensure handleSubmit Submits URL When Params are set in NextJS?

I am working on a React component where I want to submit a URL with the active filters applied when a button is clicked. However, my handleSubmit function does not seem to submit the URL correctly when the parameters are set.

What I’ve Tried

  1. Ensuring that hasMounted is correctly set and used in useEffect.
  2. Verifying that filters and defaultFilters contain the expected values.
  3. Logging activeFilters and the constructed URL to confirm they are correctly populated.

The Issue And What I Need Help With

The URL is not being submitted as expected when the handleSubmit function is called. The router.push(url) doesn’t seem to trigger the navigation to the new URL with the query parameters.

Any guidance or examples would be greatly appreciated!

My code for reference

"use client";

import React, { useState, useEffect, useRef } from "react";
import { useRouter, usePathname, useSearchParams } from "next/navigation";
import MultiRangeSlider from "../../inputs/MultiRangeSlider";
import MultipleSelect from "../../inputs/MultipleSelect";
import TagInput from "../../inputs/TagInput";
import Switch from "../../Switch";
import Select from "../../inputs/Select";
import Counter from "../../inputs/Counter";
import qs from "query-string";

import { facilitiesList } from "@/app/datasets/facilitiesList";
import { servicesList } from "@/app/datasets/servicesList";
import { categoriesList } from "@/app/datasets/categoriesList";
import { useSelectedOption } from "@/app/providers/SelectedOptionProvider";
import Input from "../../inputs/Input";

interface FilterWidgetProps {
  cityName: string;
  initialSizeRange?: number[];
  initialRentRange?: number[];
}

interface Filters {
  description: string;
  minRent: number;
  maxRent: number;
  minSize: number;
  maxSize: number;
  onlyAvailable: boolean;
  availableFrom: string;
  rentalPeriod: string;
  area: string[];
  minRooms: number;
  minToilets: number;
  minKitchens: number;
  minBathrooms: number;
  facilities: string[];
  services: string[];
  categories: string[];
}

const FilterWidget: React.FC<FilterWidgetProps> = ({
  cityName,
  initialRentRange = [0, 100],
  initialSizeRange = [0, 100],
}) => {
  const { selectedOption } = useSelectedOption(); // 'Privat' or 'Erhverv'
  const router = useRouter();
  const pathname = usePathname();
  const searchParams = useSearchParams();

  const defaultFilters: Filters = {
    description: "",
    minRent: initialRentRange[0],
    maxRent: initialRentRange[1],
    minSize: initialSizeRange[0],
    maxSize: initialSizeRange[1],
    onlyAvailable: false,
    availableFrom: "",
    rentalPeriod: "",
    area: [],
    minRooms: 0,
    minToilets: 0,
    minKitchens: 0,
    minBathrooms: 0,
    facilities: [],
    services: [],
    categories: [],
  };

  const [filters, setFilters] = useState<Filters>(defaultFilters);
  const prevFilters = useRef<Filters>(filters);
  const hasMounted = useRef(false);

  useEffect(() => {
    if (searchParams) {
      const initialParams = qs.parse(searchParams.toString());
      setFilters((prevFilters) => ({
        ...prevFilters,
        description:
          typeof initialParams.description === "string"
            ? initialParams.description
            : "",
        minRent: Number(initialParams.minRent) || initialRentRange[0],
        maxRent: Number(initialParams.maxRent) || initialRentRange[1],
        minSize: Number(initialParams.minSize) || initialSizeRange[0],
        maxSize: Number(initialParams.maxSize) || initialSizeRange[1],
        onlyAvailable: initialParams.onlyAvailable === "true" || false,
        availableFrom:
          typeof initialParams.availableFrom === "string"
            ? initialParams.availableFrom
            : "",
        rentalPeriod:
          typeof initialParams.rentalPeriod === "string"
            ? initialParams.rentalPeriod
            : "",
        area:
          typeof initialParams.area === "string"
            ? initialParams.area.split(",")
            : [],
        minRooms: Number(initialParams.minRooms) || 0,
        minToilets: Number(initialParams.minToilets) || 0,
        minKitchens: Number(initialParams.minKitchens) || 0,
        minBathrooms: Number(initialParams.minBathrooms) || 0,
        facilities:
          typeof initialParams.facilities === "string"
            ? initialParams.facilities.split(",")
            : [],
        services:
          typeof initialParams.services === "string"
            ? initialParams.services.split(",")
            : [],
        categories:
          typeof initialParams.categories === "string"
            ? initialParams.categories.split(",")
            : [],
      }));
    }
  }, [searchParams, initialRentRange, initialSizeRange]);

  useEffect(() => {
    if (hasMounted.current) {
      const activeFilters = Object.keys(filters).reduce((acc, key) => {
        if (Array.isArray(filters[key as keyof Filters])) {
          if ((filters[key as keyof Filters] as unknown as any[]).length > 0) {
            (acc as any)[key] = filters[key as keyof Filters];
          }
        } else if (
          filters[key as keyof Filters] !== defaultFilters[key as keyof Filters]
        ) {
          (acc as any)[key] = filters[key as keyof Filters];
        }
        return acc;
      }, {} as Partial<Filters>);

      const prevActiveFilters = Object.keys(prevFilters.current).reduce(
        (acc, key) => {
          if (Array.isArray(prevFilters.current[key as keyof Filters])) {
            if (
              (prevFilters.current[key as keyof Filters] as unknown as any[])
                .length > 0
            ) {
              (acc as any)[key] = prevFilters.current[key as keyof Filters];
            }
          } else if (
            prevFilters.current[key as keyof Filters] !==
            defaultFilters[key as keyof Filters]
          ) {
            (acc as any)[key] = prevFilters.current[key as keyof Filters];
          }
          return acc;
        },
        {} as Partial<Filters>
      );

      if (JSON.stringify(activeFilters) !== JSON.stringify(prevActiveFilters)) {
        const query = {
          ...activeFilters,
          area: (activeFilters.area as string[])?.join(","),
          facilities: (activeFilters.facilities as string[])?.join(","),
          services: (activeFilters.services as string[])?.join(","),
          categories: (activeFilters.categories as string[])?.join(","),
        };

        const url = `${pathname}?${qs.stringify(query)}`;
        window.history.pushState(null, "", url);
      }
      prevFilters.current = filters;
    } else {
      hasMounted.current = true;
    }
  }, [filters, pathname]);

  const handleFilterChange = (filterName: keyof Filters, value: any) => {
    setFilters((prevFilters) => ({
      ...prevFilters,
      [filterName]: value,
    }));
  };

  const handleSubmit = () => {
    const activeFilters = Object.keys(filters).reduce((acc, key) => {
      if (Array.isArray(filters[key as keyof Filters])) {
        if ((filters[key as keyof Filters] as unknown as any[]).length > 0) {
          (acc as any)[key] = filters[key as keyof Filters];
        }
      } else if (
        filters[key as keyof Filters] !== defaultFilters[key as keyof Filters]
      ) {
        (acc as any)[key] = filters[key as keyof Filters];
      }
      return acc;
    }, {} as Partial<Filters>);

    const query = {
      ...activeFilters,
      area: (activeFilters.area as string[])?.join(","),
      facilities: (activeFilters.facilities as string[])?.join(","),
      services: (activeFilters.services as string[])?.join(","),
      categories: (activeFilters.categories as string[])?.join(","),
    };

    const url = `${pathname}?${qs.stringify(query)}`;
    console.log("Submitting URL:", url); // Debugging statement
    router.push(url);
  };

  return (
    <div className="p-4 lg:py-6 lg:px-8 bg-white rounded-lg shadow-lg">
      <h4 className="mb-0 text-2xl font-semibold text-neutral-500">
        Filter properties in {cityName}
      </h4>
      <div className="my-3"></div>
      <div className="flex items-center justify-between px-4 pt-3">
        <div className="pb-10 pt-4">
          <Input
            id="description"
            label="Search Description"
            onChange={(e) => handleFilterChange("description", e.target.value)}
            name="description"
            debounce
            value={filters.description}
          />
        </div>
        <div className="pb-10 pt-4">
          <TagInput
            value={filters.area}
            onChange={(newValue) => handleFilterChange("area", newValue)}
            placeholder="Search by city or postal code"
          />
        </div>
      </div>
      <p className="mt-10 mb-4 text-neutral-500 text-xl font-medium">Rent</p>
      <div className="pb-10 pt-4">
        <MultiRangeSlider
          key="rent"
          initialMin={filters.minRent}
          initialMax={filters.maxRent}
          min={0}
          max={999999}
          onChange={(min, max) => {
            handleFilterChange("minRent", min);
            handleFilterChange("maxRent", max);
          }}
        />
      </div>
      <p className="mt-2 mb-4 text-neutral-500 text-xl font-medium">Size</p>
      <div className="pb-10 pt-4">
        <MultiRangeSlider
          key="size"
          initialMin={filters.minSize}
          initialMax={filters.maxSize}
          min={0}
          max={999999}
          onChange={(min, max) => {
            handleFilterChange("minSize", min);
            handleFilterChange("maxSize", max);
          }}
        />
      </div>
      <MultipleSelect
        label="Housing Type"
        options={categoriesList[selectedOption]}
        onChange={(newValue) => handleFilterChange("categories", newValue)}
        value={filters.categories}
      />
      <div className="my-6"></div>
      <div className="flex items-center justify-between px-4">
        <p className="mt-2 mb-4 text-neutral-500 text-xl font-medium">
          Show only available properties
        </p>
        <Switch
          checked={filters.onlyAvailable}
          onChange={(e) =>
            handleFilterChange("onlyAvailable", e.target.checked)
          }
        />
      </div>
      <div className="my-6"></div>
      <div className="flex items-center justify-between px-4">
        <Select
          id="availabilityPeriod"
          label="Period"
          name="period"
          options={[
            "Unlimited",
            "Less than 12 months",
            "12-24 months",
            "24+ months",
          ]}
          onChange={(newValue) => handleFilterChange("rentalPeriod", newValue)}
          value={filters.rentalPeriod}
        />
      </div>
      <p className="mt-10 mb-5 text-neutral-500 text-xl font-medium">
        Select minimum desired
      </p>
      <Counter
        title="Rooms"
        subtitle=""
        value={filters.minRooms}
        onChange={(newValue) => handleFilterChange("minRooms", newValue)}
      />
      <div className="my-2"></div>
      <Counter
        title="Toilets"
        subtitle=""
        value={filters.minToilets}
        onChange={(newValue) => handleFilterChange("minToilets", newValue)}
      />
      <div className="my-2"></div>
      <Counter
        title="Kitchens"
        subtitle=""
        value={filters.minKitchens}
        onChange={(newValue) => handleFilterChange("minKitchens", newValue)}
      />
      <div className="my-2"></div>
      <Counter
        title="Bathrooms"
        subtitle=""
        value={filters.minBathrooms}
        onChange={(newValue) => handleFilterChange("minBathrooms", newValue)}
      />
      <div className="mt-10 mb-5"></div>
      <MultipleSelect
        label="Facilities"
        options={facilitiesList[selectedOption]}
        onChange={(newValue) => handleFilterChange("facilities", newValue)}
        value={filters.facilities}
      />
      <div className="my-5"></div>
      <MultipleSelect
        label="Services"
        options={servicesList[selectedOption]}
        onChange={(newValue) => handleFilterChange("services", newValue)}
        value={filters.services}
      />
      <div className="border-t my-3"></div>
      <div className="border-t border-dashed my-6"></div>
      <div className="flex justify-center mt-4">
        <button
          onClick={handleSubmit}
          className="px-4 py-2 bg-blue-500 text-white rounded-lg shadow-md hover:bg-blue-600 focus:outline-none">
          Apply Filters
        </button>
      </div>
    </div>
  );
};

export default FilterWidget;

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa
Thiết kế website Thiết kế website Thiết kế website Cách kháng tài khoản quảng cáo Mua bán Fanpage Facebook Dịch vụ SEO Tổ chức sinh nhật