Fix double requests when search in order and product list (#3513)

* Fix double requests

* Fix old handle filters

* Remove not needed check in useFilterHandlers

* Test useFilterHandlers hook
This commit is contained in:
Paweł Chyła 2023-05-15 14:03:55 +02:00 committed by GitHub
parent 192be719fe
commit 0d690ff414
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 470 additions and 36 deletions

View file

@ -0,0 +1,310 @@
import { renderHook } from "@testing-library/react-hooks";
import { useFilterHandlers } from "./useFilterHandlers";
jest.mock("./useNavigator", () => () => jest.fn());
describe("useFilterHandlers", () => {
describe("resetFilters", () => {
test("should run cleanup function and call createUrl function", () => {
// Arrange
const cleanupFn = jest.fn();
const createUrl = jest.fn();
const { result } = renderHook(() =>
useFilterHandlers({
getFilterQueryParam: jest.fn(),
createUrl,
cleanupFn,
params: {
activeTab: "tab",
asc: true,
sort: "sort",
query: "query",
},
defaultSortField: "",
keepActiveTab: false,
}),
);
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const resetFilters = result.current[1];
// Act
resetFilters();
// Assert
expect(cleanupFn).toHaveBeenCalledTimes(1);
expect(createUrl).toHaveBeenCalledWith({
asc: true,
sort: "sort",
});
});
});
describe("changeFilters", () => {
test("should call cleanup function when provided", () => {
const cleanupFn = jest.fn();
const { result } = renderHook(() =>
useFilterHandlers({
getFilterQueryParam: jest.fn(),
createUrl: jest.fn(),
cleanupFn,
params: {
activeTab: "tab",
asc: true,
sort: "sort",
query: "query",
},
defaultSortField: "",
keepActiveTab: false,
}),
);
const [changeFilters] = result.current;
// Act
changeFilters([]);
// Assert
expect(cleanupFn).toHaveBeenCalledTimes(1);
});
test("should call createUrl function with with proper params when no filters", () => {
const createUrl = jest.fn();
const { result } = renderHook(() =>
useFilterHandlers({
getFilterQueryParam: jest.fn(filter => ({
[filter.name]: filter.value,
})),
createUrl,
params: {
activeTab: "tab",
asc: true,
sort: "sort",
query: "query",
},
defaultSortField: "",
keepActiveTab: false,
}),
);
const [changeFilters] = result.current;
// Act
changeFilters([]);
// Assert
expect(createUrl).toHaveBeenCalledWith({
asc: true,
sort: "sort",
query: "query",
activeTab: undefined,
});
});
test("should call createUrl function with with proper params when filters selected", () => {
const createUrl = jest.fn();
const { result } = renderHook(() =>
useFilterHandlers({
getFilterQueryParam: jest.fn(filter => ({
[filter.name]: filter.value[0],
})),
createUrl,
params: {
activeTab: "tab",
asc: true,
sort: "sort",
query: "query",
},
defaultSortField: "",
keepActiveTab: false,
}),
);
const [changeFilters] = result.current;
// Act
changeFilters([
{
name: "filter",
value: ["value"],
label: "test",
active: true,
multiple: false,
},
{
name: "filterOther",
value: ["valueOther"],
label: "test",
active: true,
multiple: false,
},
]);
// Assert
expect(createUrl).toHaveBeenCalledWith({
filter: "value",
filterOther: "valueOther",
asc: true,
sort: "sort",
query: "query",
activeTab: undefined,
});
});
test("should call createUrl function with active tab value when keepActiveTab is true", () => {
const createUrl = jest.fn();
const { result } = renderHook(() =>
useFilterHandlers({
getFilterQueryParam: jest.fn(filter => ({
[filter.name]: filter.value[0],
})),
createUrl,
params: {
activeTab: "tab",
asc: true,
sort: "sort",
query: "query",
},
defaultSortField: "",
keepActiveTab: true,
}),
);
const [changeFilters] = result.current;
// Act
changeFilters([
{
name: "filter",
value: ["value"],
label: "test",
active: true,
multiple: false,
},
]);
// Assert
expect(createUrl).toHaveBeenCalledWith({
filter: "value",
asc: true,
sort: "sort",
query: "query",
activeTab: "tab",
});
});
});
describe("handleSearchChange", () => {
test("should call createUrl function when provided", () => {
const cleanupFn = jest.fn();
const { result } = renderHook(() =>
useFilterHandlers({
getFilterQueryParam: jest.fn(),
createUrl: jest.fn(),
cleanupFn,
params: {
activeTab: "tab",
asc: true,
sort: "sort",
query: "query",
},
defaultSortField: "",
keepActiveTab: false,
}),
);
const handleSearchChange = result.current[2];
// Act
handleSearchChange("queryTest");
// Assert
expect(cleanupFn).toHaveBeenCalledTimes(1);
});
test("should run createUrl function with params and query", () => {
const createUrl = jest.fn();
const { result } = renderHook(() =>
useFilterHandlers({
getFilterQueryParam: jest.fn(filter => ({
[filter.name]: filter.value[0],
})),
createUrl,
params: {
activeTab: "tab",
asc: true,
sort: "sort",
query: "query",
},
defaultSortField: "",
keepActiveTab: true,
}),
);
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const handleSearchChange = result.current[2];
// Act
handleSearchChange("queryTest");
// Assert
expect(createUrl).toHaveBeenCalledWith({
after: undefined,
before: undefined,
asc: true,
sort: "sort",
query: "queryTest",
activeTab: undefined,
});
});
test("should run createUrl function with sort rank and asc false when hasSortWithRank is true", () => {
const createUrl = jest.fn();
const { result } = renderHook(() =>
useFilterHandlers({
getFilterQueryParam: jest.fn(filter => ({
[filter.name]: filter.value[0],
})),
createUrl,
params: {
activeTab: "tab",
asc: true,
sort: "sort",
query: "query",
},
hasSortWithRank: true,
defaultSortField: "",
keepActiveTab: true,
}),
);
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const handleSearchChange = result.current[2];
// Act
handleSearchChange("queryTest");
// Assert
expect(createUrl).toHaveBeenCalledWith({
after: undefined,
before: undefined,
asc: false,
sort: "rank",
query: "queryTest",
activeTab: undefined,
});
});
});
});

View file

@ -0,0 +1,141 @@
import { IFilter } from "@dashboard/components/Filter";
import { ActiveTab, Pagination, Search, Sort } from "@dashboard/types";
import {
GetFilterQueryParam,
getFilterQueryParams,
} from "@dashboard/utils/filters";
import { useEffect, useRef } from "react";
import useNavigator from "./useNavigator";
type RequiredParams = ActiveTab &
Search &
Sort<any> &
Pagination & { presestesChanged?: string };
type CreateUrl = (params: RequiredParams) => string;
type CreateFilterHandlers<TFilterKeys extends string> = [
(filter: IFilter<TFilterKeys>) => void,
() => void,
(query: string) => void,
];
export const useFilterHandlers = <
TFilterKeys extends string,
TFilters extends {},
SortField extends string,
>(opts: {
getFilterQueryParam: GetFilterQueryParam<TFilterKeys, TFilters>;
createUrl: CreateUrl;
params: RequiredParams;
cleanupFn?: () => void;
keepActiveTab?: boolean;
defaultSortField: SortField;
hasSortWithRank?: boolean;
}): CreateFilterHandlers<TFilterKeys> => {
const {
getFilterQueryParam,
createUrl,
params,
cleanupFn,
keepActiveTab,
defaultSortField,
hasSortWithRank = false,
} = opts;
const navigate = useNavigator();
const prevAsc = useRef<boolean | null>(null);
useEffect(() => {
const hasQuery = !!params.query?.trim();
if (hasQuery || params.sort === "rank") {
prevAsc.current = params.asc;
}
}, [params.asc, params.query, params.sort]);
const getActiveTabValue = (removeActiveTab: boolean) => {
if (!keepActiveTab || removeActiveTab) {
return undefined;
}
return params.activeTab;
};
const changeFilters = (filters: IFilter<TFilterKeys>) => {
if (!!cleanupFn) {
cleanupFn();
}
const filtersQueryParams = getFilterQueryParams(
filters,
getFilterQueryParam,
);
navigate(
createUrl({
...params,
...filtersQueryParams,
activeTab: getActiveTabValue(
checkIfParamsEmpty(filtersQueryParams) && !params.query?.length,
),
}),
);
};
const resetFilters = () => {
if (!!cleanupFn) {
cleanupFn();
}
navigate(
createUrl({
asc: params.asc,
sort: params.sort,
}),
);
};
const handleSearchChange = (query: string) => {
if (!!cleanupFn) {
cleanupFn();
}
const trimmedQuery = query?.trim() ?? "";
const hasQuery = !!trimmedQuery;
const sortWithoutQuery =
params.sort === "rank" ? defaultSortField : params.sort;
const sortWithQuery = "rank" as SortField;
const getAscParam = () => {
if (hasQuery) {
return false;
}
if (prevAsc !== null) {
return true;
}
return params.asc;
};
navigate(
createUrl({
...params,
after: undefined,
before: undefined,
activeTab: getActiveTabValue(checkIfParamsEmpty(params) && hasQuery),
query: hasQuery ? trimmedQuery : undefined,
...(hasSortWithRank && {
sort: hasQuery ? sortWithQuery : sortWithoutQuery,
asc: getAscParam(),
}),
}),
);
};
return [changeFilters, resetFilters, handleSearchChange];
};
function checkIfParamsEmpty(params: RequiredParams): boolean {
const paramsToOmit = ["activeTab", "sort", "asc", "query"];
return Object.entries(params)
.filter(([name]) => !paramsToOmit.includes(name))
.every(([_, value]) => value === undefined);
}

View file

@ -9,6 +9,7 @@ import {
useOrderDraftCreateMutation,
useOrderListQuery,
} from "@dashboard/graphql";
import { useFilterHandlers } from "@dashboard/hooks/useFilterHandlers";
import useListSettings from "@dashboard/hooks/useListSettings";
import useNavigator from "@dashboard/hooks/useNavigator";
import useNotifier from "@dashboard/hooks/useNotifier";
@ -17,7 +18,6 @@ import usePaginator, {
createPaginationState,
PaginatorContext,
} from "@dashboard/hooks/usePaginator";
import { useSortRedirects } from "@dashboard/hooks/useSortRedirects";
import {
getActiveTabIndexAfterTabDelete,
getNextUniqueTabName,
@ -25,7 +25,6 @@ import {
import { ListViews } from "@dashboard/types";
import { prepareQs } from "@dashboard/utils/filters/qs";
import createDialogActionHandlers from "@dashboard/utils/handlers/dialogActionHandlers";
import createFilterHandlers from "@dashboard/utils/handlers/filterHandlers";
import createSortHandler from "@dashboard/utils/handlers/sortHandler";
import { mapEdgesToItems, mapNodeToChoice } from "@dashboard/utils/maps";
import { getSortParams } from "@dashboard/utils/sort";
@ -38,7 +37,6 @@ import {
orderListUrl,
OrderListUrlDialog,
OrderListUrlQueryParams,
OrderListUrlSortField,
orderSettingsPath,
orderUrl,
} from "../../urls";
@ -100,14 +98,13 @@ export const OrderList: React.FC<OrderListProps> = ({ params }) => {
const currentTab =
params.activeTab !== undefined ? parseInt(params.activeTab, 10) : undefined;
const [changeFilters, resetFilters, handleSearchChange] =
createFilterHandlers({
createUrl: orderListUrl,
getFilterQueryParam,
navigate,
params,
keepActiveTab: true,
});
const [changeFilters, resetFilters, handleSearchChange] = useFilterHandlers({
createUrl: orderListUrl,
getFilterQueryParam,
params,
defaultSortField: DEFAULT_SORT_KEY,
hasSortWithRank: true,
});
const [openModal, closeModal] = createDialogActionHandlers<
OrderListUrlDialog,
@ -196,12 +193,6 @@ export const OrderList: React.FC<OrderListProps> = ({ params }) => {
const handleSort = createSortHandler(navigate, orderListUrl, params);
useSortRedirects<OrderListUrlSortField>({
params,
defaultSortField: DEFAULT_SORT_KEY,
urlFunc: orderListUrl,
});
return (
<PaginatorContext.Provider value={paginationValues}>
<OrderListPage

View file

@ -27,6 +27,7 @@ import {
useWarehouseListQuery,
} from "@dashboard/graphql";
import useBackgroundTask from "@dashboard/hooks/useBackgroundTask";
import { useFilterHandlers } from "@dashboard/hooks/useFilterHandlers";
import useListSettings from "@dashboard/hooks/useListSettings";
import useNavigator from "@dashboard/hooks/useNavigator";
import useNotifier from "@dashboard/hooks/useNotifier";
@ -59,7 +60,6 @@ import useProductTypeSearch from "@dashboard/searches/useProductTypeSearch";
import { ListViews } from "@dashboard/types";
import { prepareQs } from "@dashboard/utils/filters/qs";
import createDialogActionHandlers from "@dashboard/utils/handlers/dialogActionHandlers";
import createFilterHandlers from "@dashboard/utils/handlers/filterHandlers";
import { mapEdgesToItems, mapNodeToChoice } from "@dashboard/utils/maps";
import { getSortUrlVariables } from "@dashboard/utils/sort";
import { DialogContentText } from "@material-ui/core";
@ -68,7 +68,6 @@ import { stringify } from "qs";
import React, { useCallback, useEffect, useRef, useState } from "react";
import { FormattedMessage, useIntl } from "react-intl";
import { useSortRedirects } from "../../../hooks/useSortRedirects";
import ProductListPage from "../../components/ProductListPage";
import {
deleteFilterTab,
@ -79,7 +78,7 @@ import {
saveFilterTab,
updateFilterTab,
} from "./filters";
import { canBeSorted, DEFAULT_SORT_KEY, getSortQueryVariables } from "./sort";
import { DEFAULT_SORT_KEY, getSortQueryVariables } from "./sort";
import {
getActiveTabIndexAfterTabDelete,
getAvailableProductKinds,
@ -202,13 +201,6 @@ export const ProductList: React.FC<ProductListProps> = ({ params }) => {
channel => channel.slug === params.channel,
);
useSortRedirects<ProductListUrlSortField>({
params,
defaultSortField: DEFAULT_SORT_KEY,
urlFunc: productListUrl,
resetToDefault: !canBeSorted(params.sort, !!selectedChannel),
});
const [openModal, closeModal] = createDialogActionHandlers<
ProductListUrlDialog,
ProductListUrlQueryParams
@ -263,15 +255,15 @@ export const ProductList: React.FC<ProductListProps> = ({ params }) => {
},
});
const [changeFilters, resetFilters, handleSearchChange] =
createFilterHandlers({
cleanupFn: clearRowSelection,
createUrl: productListUrl,
getFilterQueryParam,
navigate,
params,
keepActiveTab: true,
});
const [changeFilters, resetFilters, handleSearchChange] = useFilterHandlers({
cleanupFn: clearRowSelection,
createUrl: productListUrl,
getFilterQueryParam,
params,
keepActiveTab: true,
defaultSortField: DEFAULT_SORT_KEY,
hasSortWithRank: true,
});
const handleTabChange = (tab: number) => {
clearRowSelection();