fix: error about page number persistence when filters change (#7655)

This commit is contained in:
Alejandro Bailo
2025-05-05 12:23:04 +02:00
committed by Pepe Fagoaga
parent dc3d5149e9
commit 635b3f1978
6 changed files with 97 additions and 50 deletions
+7 -7
View File
@@ -8,12 +8,14 @@ import {
} from "@internationalized/date";
import { Button, ButtonGroup, DatePicker } from "@nextui-org/react";
import { useLocale } from "@react-aria/i18n";
import { useRouter, useSearchParams } from "next/navigation";
import { useSearchParams } from "next/navigation";
import React, { useCallback, useEffect, useRef } from "react";
import { useUrlFilters } from "@/hooks/use-url-filters";
export const CustomDatePicker = () => {
const router = useRouter();
const searchParams = useSearchParams();
const { updateFilter } = useUrlFilters();
const [value, setValue] = React.useState(() => {
const dateParam = searchParams.get("filter[inserted_at]");
@@ -28,15 +30,13 @@ export const CustomDatePicker = () => {
const applyDateFilter = useCallback(
(date: any) => {
const params = new URLSearchParams(searchParams.toString());
if (date) {
params.set("filter[inserted_at]", date.toString());
updateFilter("inserted_at", date.toString());
} else {
params.delete("filter[inserted_at]");
updateFilter("inserted_at", null);
}
router.push(`?${params.toString()}`, { scroll: false });
},
[router, searchParams],
[updateFilter],
);
const initialRender = useRef(true);
@@ -1,26 +1,25 @@
import { Input } from "@nextui-org/react";
import debounce from "lodash.debounce";
import { SearchIcon, XCircle } from "lucide-react";
import { useRouter, useSearchParams } from "next/navigation";
import { useSearchParams } from "next/navigation";
import React, { useCallback, useEffect, useState } from "react";
export const CustomSearchInput: React.FC = () => {
const router = useRouter();
const searchParams = useSearchParams();
import { useUrlFilters } from "@/hooks/use-url-filters";
export const CustomSearchInput: React.FC = () => {
const searchParams = useSearchParams();
const { updateFilter } = useUrlFilters();
const [searchQuery, setSearchQuery] = useState("");
const applySearch = useCallback(
(query: string) => {
const params = new URLSearchParams(searchParams.toString());
if (query) {
params.set("filter[search]", query);
updateFilter("search", query);
} else {
params.delete("filter[search]");
updateFilter("search", null);
}
router.push(`?${params.toString()}`, { scroll: false });
},
[router, searchParams],
[updateFilter],
);
const debouncedChangeHandler = useCallback(
+4 -13
View File
@@ -1,8 +1,9 @@
"use client";
import { useRouter, useSearchParams } from "next/navigation";
import React, { useCallback, useEffect, useState } from "react";
import { useSearchParams } from "next/navigation";
import React, { useEffect, useState } from "react";
import { useUrlFilters } from "@/hooks/use-url-filters";
import { FilterControlsProps } from "@/types";
import { CrossIcon } from "../icons";
@@ -24,8 +25,8 @@ export const FilterControls: React.FC<FilterControlsProps> = ({
mutedFindings = false,
customFilters,
}) => {
const router = useRouter();
const searchParams = useSearchParams();
const { clearAllFilters } = useUrlFilters();
const [showClearButton, setShowClearButton] = useState(false);
useEffect(() => {
@@ -35,16 +36,6 @@ export const FilterControls: React.FC<FilterControlsProps> = ({
setShowClearButton(hasFilters);
}, [searchParams]);
const clearAllFilters = useCallback(() => {
const params = new URLSearchParams(searchParams.toString());
Array.from(params.keys()).forEach((key) => {
if (key.startsWith("filter[") || key === "sort") {
params.delete(key);
}
});
router.push(`?${params.toString()}`, { scroll: false });
}, [router, searchParams]);
return (
<div className="flex flex-col gap-4">
<div className="grid grid-cols-1 items-center gap-x-4 gap-y-4 md:grid-cols-2 xl:grid-cols-4">
@@ -11,10 +11,11 @@ import {
ScrollShadow,
} from "@nextui-org/react";
import { XCircle } from "lucide-react";
import { useRouter, useSearchParams } from "next/navigation";
import { useSearchParams } from "next/navigation";
import React, { useCallback, useEffect, useMemo, useState } from "react";
import { PlusCircleIcon } from "@/components/icons";
import { useUrlFilters } from "@/hooks/use-url-filters";
import { CustomDropdownFilterProps } from "@/types";
const filterSelectedClass =
@@ -24,8 +25,8 @@ export const CustomDropdownFilter: React.FC<CustomDropdownFilterProps> = ({
filter,
onFilterChange,
}) => {
const router = useRouter();
const searchParams = useSearchParams();
const { clearFilter } = useUrlFilters();
const [groupSelected, setGroupSelected] = useState(new Set<string>());
const [pendingClearFilter, setPendingClearFilter] = useState<string | null>(
null,
@@ -114,13 +115,11 @@ export const CustomDropdownFilter: React.FC<CustomDropdownFilterProps> = ({
// Execute the update in the router after the render
useEffect(() => {
if (pendingClearFilter) {
const params = new URLSearchParams(searchParams.toString());
params.delete(`filter[${pendingClearFilter}]`);
router.push(`?${params.toString()}`, { scroll: false });
if (pendingClearFilter && filter) {
clearFilter(pendingClearFilter);
setPendingClearFilter(null); // Reset the state
}
}, [pendingClearFilter, searchParams, router]);
}, [pendingClearFilter, clearFilter, filter]);
return (
<div className="relative flex w-full flex-col gap-2">
@@ -1,11 +1,11 @@
"use client";
import { useRouter, useSearchParams } from "next/navigation";
import React, { useState } from "react";
import { useCallback } from "react";
import { CustomFilterIcon } from "@/components/icons";
import { CustomButton, CustomDropdownFilter } from "@/components/ui/custom";
import { useUrlFilters } from "@/hooks/use-url-filters";
import { FilterOption } from "@/types";
export interface DataTableFilterCustomProps {
@@ -17,24 +17,14 @@ export const DataTableFilterCustom = ({
filters,
defaultOpen = false,
}: DataTableFilterCustomProps) => {
const router = useRouter();
const searchParams = useSearchParams();
const { updateFilter } = useUrlFilters();
const [showFilters, setShowFilters] = useState(defaultOpen);
const pushDropdownFilter = useCallback(
(key: string, values: string[]) => {
const params = new URLSearchParams(searchParams);
const filterKey = `filter[${key}]`;
if (values.length === 0) {
params.delete(filterKey);
} else {
params.set(filterKey, values.join(","));
}
router.push(`?${params.toString()}`);
updateFilter(key, values.length > 0 ? values : null);
},
[router, searchParams],
[updateFilter],
);
return (
+68
View File
@@ -0,0 +1,68 @@
"use client";
import { usePathname, useRouter, useSearchParams } from "next/navigation";
import { useCallback } from "react";
/**
* Custom hook to handle URL filters and automatically reset
* pagination when filters change.
*/
export const useUrlFilters = () => {
const router = useRouter();
const searchParams = useSearchParams();
const pathname = usePathname();
const updateFilter = useCallback(
(key: string, value: string | string[] | null) => {
const params = new URLSearchParams(searchParams.toString());
// Always reset page to 1 when a filter is applied
params.set("page", "1");
const filterKey = key.startsWith("filter[") ? key : `filter[${key}]`;
if (value === null || (Array.isArray(value) && value.length === 0)) {
params.delete(filterKey);
} else if (Array.isArray(value)) {
params.set(filterKey, value.join(","));
} else {
params.set(filterKey, value);
}
router.push(`${pathname}?${params.toString()}`, { scroll: false });
},
[router, searchParams, pathname],
);
const clearFilter = useCallback(
(key: string) => {
const params = new URLSearchParams(searchParams.toString());
const filterKey = key.startsWith("filter[") ? key : `filter[${key}]`;
params.delete(filterKey);
params.set("page", "1");
router.push(`${pathname}?${params.toString()}`, { scroll: false });
},
[router, searchParams, pathname],
);
const clearAllFilters = useCallback(() => {
const params = new URLSearchParams(searchParams.toString());
Array.from(params.keys()).forEach((key) => {
if (key.startsWith("filter[") || key === "sort") {
params.delete(key);
}
});
params.delete("page");
router.push(`${pathname}?${params.toString()}`, { scroll: false });
}, [router, searchParams, pathname]);
return {
updateFilter,
clearFilter,
clearAllFilters,
};
};