Peter Makowski has proposed merging ~petermakowski/maas-site-manager:debounce-search-requests-MAASENG-1481 into maas-site-manager:main.
Commit message: feat: debounce search requests - add useDebouncedValue hook - add @testing-library/react-hooks Requested reviews: MAAS Committers (maas-committers) For more details, see: https://code.launchpad.net/~petermakowski/maas-site-manager/+git/site-manager/+merge/438880 -- Your team MAAS Committers is requested to review the proposed merge of ~petermakowski/maas-site-manager:debounce-search-requests-MAASENG-1481 into maas-site-manager:main.
diff --git a/frontend/package.json b/frontend/package.json index 75a5826..757e451 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -32,6 +32,7 @@ "@remix-run/web-fetch": "4.3.2", "@testing-library/jest-dom": "5.16.5", "@testing-library/react": "13.4.0", + "@testing-library/react-hooks": "8.0.1", "@testing-library/user-event": "14.4.3", "@types/axios": "0.14.0", "@types/chance": "1.1.3", diff --git a/frontend/src/components/Navigation/Navigation.test.tsx b/frontend/src/components/Navigation/Navigation.test.tsx index 549dcdc..ab2baed 100644 --- a/frontend/src/components/Navigation/Navigation.test.tsx +++ b/frontend/src/components/Navigation/Navigation.test.tsx @@ -1,9 +1,8 @@ -import userEvent from "@testing-library/user-event"; import { MemoryRouter } from "react-router-dom"; import Navigation from "./Navigation"; -import { render, screen } from "@/test-utils"; +import { render, screen, userEvent } from "@/test-utils"; describe("Navigation", () => { it("displays navigation", () => { diff --git a/frontend/src/components/SitesList/SitesList.tsx b/frontend/src/components/SitesList/SitesList.tsx index 8529e5f..f47b9ed 100644 --- a/frontend/src/components/SitesList/SitesList.tsx +++ b/frontend/src/components/SitesList/SitesList.tsx @@ -5,6 +5,7 @@ import { Pagination } from "@canonical/react-components"; import SitesTable from "./components/SitesTable"; import { useSitesQuery } from "@/hooks/api"; +import useDebounce from "@/hooks/useDebouncedValue"; import { parseSearchTextToQueryParams } from "@/utils"; const DEFAULT_PAGE_SIZE = 50; @@ -13,9 +14,11 @@ const SitesList = () => { const [page, setPage] = useState(0); const [size] = useState(DEFAULT_PAGE_SIZE); const [searchText, setSearchText] = useState(""); + const debounceSearchText = useDebounce(searchText); + const { data, isLoading, isFetchedAfterMount } = useSitesQuery( { page: `${page}`, size: `${size}` }, - parseSearchTextToQueryParams(searchText), + parseSearchTextToQueryParams(debounceSearchText), ); useEffect(() => { diff --git a/frontend/src/hooks/useDebouncedValue.test.ts b/frontend/src/hooks/useDebouncedValue.test.ts new file mode 100644 index 0000000..ef6d4b8 --- /dev/null +++ b/frontend/src/hooks/useDebouncedValue.test.ts @@ -0,0 +1,50 @@ +import { vi } from "vitest"; + +import useDebouncedValue from "./useDebouncedValue"; + +import { renderHook } from "@/test-utils"; + +describe("useDebouncedValue", () => { + beforeAll(() => { + vi.useFakeTimers(); + }); + + afterEach(() => { + vi.clearAllTimers(); + }); + + afterAll(() => { + vi.useRealTimers(); + }); + + it("returns debounced value", async () => { + const { result, rerender } = renderHook(({ value }) => useDebouncedValue(value), { + initialProps: { + value: "value", + }, + }); + + expect(result.current).toBe("value"); + + await rerender({ value: "new-value" }); + await vi.advanceTimersToNextTimer(); + + expect(result.current).toBe("new-value"); + }); + + it("accepts custom delay", async () => { + const { result, rerender } = renderHook(({ value, delay }) => useDebouncedValue(value, delay), { + initialProps: { + value: "value", + delay: 5, + }, + }); + + expect(result.current).toBe("value"); + + await rerender({ value: "new-value", delay: 5 }); + await vi.advanceTimersByTime(5); + + expect(result.current).toBe("new-value"); + }); +}); diff --git a/frontend/src/hooks/useDebouncedValue.ts b/frontend/src/hooks/useDebouncedValue.ts new file mode 100644 index 0000000..509f673 --- /dev/null +++ b/frontend/src/hooks/useDebouncedValue.ts @@ -0,0 +1,18 @@ +import { useEffect, useState } from "react"; + +export const DEFAULT_DELAY = 500; + +function useDebouncedValue<T>(value: T, delay?: number): T { + const [debouncedValue, setDebouncedValue] = useState<T>(value); + + useEffect(() => { + const timeoutId = setTimeout(() => setDebouncedValue(value), delay || DEFAULT_DELAY); + return () => { + clearTimeout(timeoutId); + }; + }, [value, delay]); + + return debouncedValue; +} + +export default useDebouncedValue; diff --git a/frontend/src/test-utils.tsx b/frontend/src/test-utils.tsx index 50437b8..450b8d7 100644 --- a/frontend/src/test-utils.tsx +++ b/frontend/src/test-utils.tsx @@ -44,5 +44,7 @@ const renderWithMemoryRouter = (ui: ReactElement, options?: MemoryRenderOptions) export { screen, within, waitFor } from "@testing-library/react"; export { customRender as render }; +export { renderHook } from "@testing-library/react-hooks"; +export { default as userEvent } from "@testing-library/user-event"; export { renderWithMemoryRouter }; export { Providers }; diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 5c9a9a9..5f07629 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -1549,6 +1549,14 @@ lodash "^4.17.15" redent "^3.0.0" +"@testing-library/react-hooks@8.0.1": + version "8.0.1" + resolved "https://registry.yarnpkg.com/@testing-library/react-hooks/-/react-hooks-8.0.1.tgz#0924bbd5b55e0c0c0502d1754657ada66947ca12" + integrity sha512-Aqhl2IVmLt8IovEVarNDFuJDVWVvhnr9/GCU6UUnrYXwgDFF9h2L2o2P9KBni1AST5sT6riAyoukFLyjQUgD/g== + dependencies: + "@babel/runtime" "^7.12.5" + react-error-boundary "^3.1.0" + "@testing-library/react@13.4.0": version "13.4.0" resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-13.4.0.tgz#6a31e3bf5951615593ad984e96b9e5e2d9380966" @@ -5167,6 +5175,13 @@ react-dom@18.2.0: loose-envify "^1.1.0" scheduler "^0.23.0" +react-error-boundary@^3.1.0: + version "3.1.4" + resolved "https://registry.yarnpkg.com/react-error-boundary/-/react-error-boundary-3.1.4.tgz#255db92b23197108757a888b01e5b729919abde0" + integrity sha512-uM9uPzZJTF6wRQORmSrvOIgt4lJ9MC1sNgEOj2XGsDTRE4kmpWxg7ENK9EWNKJRMAOY9z0MuF4yIfl6gp4sotA== + dependencies: + "@babel/runtime" "^7.12.5" + react-is@^16.13.1: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
-- Mailing list: https://launchpad.net/~sts-sponsors Post to : sts-sponsors@lists.launchpad.net Unsubscribe : https://launchpad.net/~sts-sponsors More help : https://help.launchpad.net/ListHelp