From 57036afb825f2031e92992bf4a68a67d3a18b174 Mon Sep 17 00:00:00 2001 From: Sean Heckathorne Date: Sat, 8 Jun 2024 06:19:34 -0500 Subject: [PATCH 1/4] initial commit --- .../Handler/Search/HandlerSearchForm.tsx | 20 +++--- client/src/hooks/index.ts | 1 + .../hooks/useDebounce/useDebounce.spec.tsx | 72 +++++++++++++++++++ client/src/hooks/useDebounce/useDebounce.tsx | 17 +++++ 4 files changed, 101 insertions(+), 9 deletions(-) create mode 100644 client/src/hooks/useDebounce/useDebounce.spec.tsx create mode 100644 client/src/hooks/useDebounce/useDebounce.tsx diff --git a/client/src/components/Manifest/Handler/Search/HandlerSearchForm.tsx b/client/src/components/Manifest/Handler/Search/HandlerSearchForm.tsx index fa91327f..ecbdc372 100644 --- a/client/src/components/Manifest/Handler/Search/HandlerSearchForm.tsx +++ b/client/src/components/Manifest/Handler/Search/HandlerSearchForm.tsx @@ -15,6 +15,7 @@ import { import { useSearchParams } from 'react-router-dom'; import Select from 'react-select'; import { useGetProfileQuery, useSearchRcrainfoSitesQuery, useSearchRcraSitesQuery } from 'store'; +import { useDebounce } from 'hooks'; interface Props { handleClose: () => void; @@ -37,19 +38,20 @@ export function HandlerSearchForm({ const { handleSubmit, control } = useForm(); const manifestForm = useFormContext(); const [inputValue, setInputValue] = useState(''); + const debouncedInputValue = useDebounce(inputValue, 500); + const [selectedHandler, setSelectedHandler] = useState(null); const { org } = useGetProfileQuery(undefined, { selectFromResult: ({ data }) => { return { org: data?.org }; }, }); - const [skip, setSkip] = useState(true); const { data } = useSearchRcraSitesQuery( { siteType: handlerType, - siteId: inputValue, + siteId: debouncedInputValue, }, - { skip } + { skip: false } ); const { data: rcrainfoData, @@ -58,9 +60,9 @@ export function HandlerSearchForm({ } = useSearchRcrainfoSitesQuery( { siteType: handlerType, - siteId: inputValue, + siteId: debouncedInputValue, }, - { skip: skip || !org?.rcrainfoIntegrated } + { skip: !org?.rcrainfoIntegrated } ); const { setGeneratorStateCode, setTsdfStateCode } = useContext(ManifestContext); @@ -95,10 +97,10 @@ export function HandlerSearchForm({ handleClose(); }; - useEffect(() => { - const inputTooShort = inputValue.length < 5; - setSkip(inputTooShort); - }, [inputValue]); + // useEffect(() => { + // const inputTooShort = inputValue.length < 1; + // setSkip(inputTooShort); + // }, [debouncedInputValue]); useEffect(() => { const knownSites = data && data.length > 0 ? data : []; diff --git a/client/src/hooks/index.ts b/client/src/hooks/index.ts index f502f570..223078c1 100644 --- a/client/src/hooks/index.ts +++ b/client/src/hooks/index.ts @@ -2,3 +2,4 @@ export { usePagination } from './usePagination/usePagination'; export { useProgressTracker } from './useProgressTracker/useProgressTracker'; export { useTitle } from './useTitle/useTitle'; export { useUserSiteIds } from './useUserSiteIds/useUserSiteIds'; +export { useDebounce } from './useDebounce/useDebounce'; diff --git a/client/src/hooks/useDebounce/useDebounce.spec.tsx b/client/src/hooks/useDebounce/useDebounce.spec.tsx new file mode 100644 index 00000000..d0595cfa --- /dev/null +++ b/client/src/hooks/useDebounce/useDebounce.spec.tsx @@ -0,0 +1,72 @@ +import '@testing-library/jest-dom'; +import { renderHook, act } from '@testing-library/react'; +import { useDebounce } from './useDebounce'; +import { beforeAll, afterAll, afterEach, describe, expect, it, vi } from 'vitest'; + +describe('useDebounce hook', () => { + beforeAll(() => { + vi.useFakeTimers(); // Use fake timers + }); + + afterAll(() => { + vi.clearAllTimers(); // Clear all timers after tests + }); + + it('should return the initial value immediately', () => { + const { result } = renderHook(() => useDebounce('initial', 500)); + expect(result.current).toBe('initial'); + }); + + it('should update the debounced value after the specified delay', () => { + const { result, rerender } = renderHook(({ value, delay }) => useDebounce(value, delay), { + initialProps: { value: 'initial', delay: 500 }, + }); + + // Update the value + rerender({ value: 'updated', delay: 500 }); + + // Before the delay, the debounced value should still be 'initial' + expect(result.current).toBe('initial'); + + // Fast forward time by 500ms + act(() => { + vi.advanceTimersByTime(500); + }); + + // After the delay, the debounced value should be 'updated' + expect(result.current).toBe('updated'); + }); + + it('should reset the timer when value changes within the delay period', () => { + const { result, rerender } = renderHook(({ value, delay }) => useDebounce(value, delay), { + initialProps: { value: 'initial', delay: 500 }, + }); + + // Update the value multiple times within the delay period + rerender({ value: 'updated1', delay: 500 }); + act(() => { + vi.advanceTimersByTime(300); + }); + rerender({ value: 'updated2', delay: 500 }); + + // Fast forward time by the remaining 200ms + act(() => { + vi.advanceTimersByTime(200); + }); + + // The debounced value should still be 'initial' because the timer was reset + expect(result.current).toBe('initial'); + + // Fast forward time by 300ms + act(() => { + vi.advanceTimersByTime(300); + }); + + // After the full delay, the debounced value should be 'updated2' + expect(result.current).toBe('updated2'); + }); + + afterEach(() => { + vi.clearAllTimers(); // Clear all timers after each test + }); +}); diff --git a/client/src/hooks/useDebounce/useDebounce.tsx b/client/src/hooks/useDebounce/useDebounce.tsx new file mode 100644 index 00000000..2ccc1d1d --- /dev/null +++ b/client/src/hooks/useDebounce/useDebounce.tsx @@ -0,0 +1,17 @@ +import { useState, useEffect } from 'react'; + +export const useDebounce = (value: string, milliSeconds: number) => { + const [debouncedValue, setDebouncedValue] = useState(value); + + useEffect(() => { + const handler = setTimeout(() => { + setDebouncedValue(value); + }, milliSeconds); + + return () => { + clearTimeout(handler); + }; + }, [value, milliSeconds]); + + return debouncedValue; +}; From b66fffc78158900640a838bb29db42dffb9885ee Mon Sep 17 00:00:00 2001 From: Sean Heckathorne Date: Mon, 10 Jun 2024 14:55:02 -0500 Subject: [PATCH 2/4] re-added 'skip' state --- .../Manifest/Handler/Search/HandlerSearchForm.tsx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/client/src/components/Manifest/Handler/Search/HandlerSearchForm.tsx b/client/src/components/Manifest/Handler/Search/HandlerSearchForm.tsx index ecbdc372..ea9575f7 100644 --- a/client/src/components/Manifest/Handler/Search/HandlerSearchForm.tsx +++ b/client/src/components/Manifest/Handler/Search/HandlerSearchForm.tsx @@ -46,6 +46,7 @@ export function HandlerSearchForm({ return { org: data?.org }; }, }); + const [skip, setSkip] = useState(true); const { data } = useSearchRcraSitesQuery( { siteType: handlerType, @@ -97,10 +98,10 @@ export function HandlerSearchForm({ handleClose(); }; - // useEffect(() => { - // const inputTooShort = inputValue.length < 1; - // setSkip(inputTooShort); - // }, [debouncedInputValue]); + useEffect(() => { + const inputTooShort = inputValue.length < 2; + setSkip(inputTooShort); + }, [debouncedInputValue]); useEffect(() => { const knownSites = data && data.length > 0 ? data : []; From 1cdd0c9ebdd38f6730e666dcccfd7e4cfd8da19b Mon Sep 17 00:00:00 2001 From: Sean Heckathorne Date: Mon, 10 Jun 2024 20:11:48 -0500 Subject: [PATCH 3/4] reapplied skip state to the search bar --- .../components/Manifest/Handler/Search/HandlerSearchForm.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/src/components/Manifest/Handler/Search/HandlerSearchForm.tsx b/client/src/components/Manifest/Handler/Search/HandlerSearchForm.tsx index ea9575f7..2945ad3b 100644 --- a/client/src/components/Manifest/Handler/Search/HandlerSearchForm.tsx +++ b/client/src/components/Manifest/Handler/Search/HandlerSearchForm.tsx @@ -52,7 +52,7 @@ export function HandlerSearchForm({ siteType: handlerType, siteId: debouncedInputValue, }, - { skip: false } + { skip } ); const { data: rcrainfoData, @@ -63,7 +63,7 @@ export function HandlerSearchForm({ siteType: handlerType, siteId: debouncedInputValue, }, - { skip: !org?.rcrainfoIntegrated } + { skip: skip || !org?.rcrainfoIntegrated } ); const { setGeneratorStateCode, setTsdfStateCode } = useContext(ManifestContext); From 903a508d95686761b1fff5454f64f0229909d009 Mon Sep 17 00:00:00 2001 From: Sean Heckathorne Date: Fri, 14 Jun 2024 03:55:17 -0400 Subject: [PATCH 4/4] remove empty epaId querystring when selecting transporter --- .../Manifest/Handler/Search/HandlerSearchForm.tsx | 2 +- client/src/hooks/useDebounce/useDebounce.spec.tsx | 15 +++------------ 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/client/src/components/Manifest/Handler/Search/HandlerSearchForm.tsx b/client/src/components/Manifest/Handler/Search/HandlerSearchForm.tsx index 2945ad3b..ba11fd22 100644 --- a/client/src/components/Manifest/Handler/Search/HandlerSearchForm.tsx +++ b/client/src/components/Manifest/Handler/Search/HandlerSearchForm.tsx @@ -52,7 +52,7 @@ export function HandlerSearchForm({ siteType: handlerType, siteId: debouncedInputValue, }, - { skip } + { skip: skip || debouncedInputValue === '' } ); const { data: rcrainfoData, diff --git a/client/src/hooks/useDebounce/useDebounce.spec.tsx b/client/src/hooks/useDebounce/useDebounce.spec.tsx index d0595cfa..0a57535f 100644 --- a/client/src/hooks/useDebounce/useDebounce.spec.tsx +++ b/client/src/hooks/useDebounce/useDebounce.spec.tsx @@ -5,11 +5,11 @@ import { beforeAll, afterAll, afterEach, describe, expect, it, vi } from 'vitest describe('useDebounce hook', () => { beforeAll(() => { - vi.useFakeTimers(); // Use fake timers + vi.useFakeTimers(); }); afterAll(() => { - vi.clearAllTimers(); // Clear all timers after tests + vi.clearAllTimers(); }); it('should return the initial value immediately', () => { @@ -22,18 +22,14 @@ describe('useDebounce hook', () => { initialProps: { value: 'initial', delay: 500 }, }); - // Update the value rerender({ value: 'updated', delay: 500 }); - // Before the delay, the debounced value should still be 'initial' expect(result.current).toBe('initial'); - // Fast forward time by 500ms act(() => { vi.advanceTimersByTime(500); }); - // After the delay, the debounced value should be 'updated' expect(result.current).toBe('updated'); }); @@ -42,31 +38,26 @@ describe('useDebounce hook', () => { initialProps: { value: 'initial', delay: 500 }, }); - // Update the value multiple times within the delay period rerender({ value: 'updated1', delay: 500 }); act(() => { vi.advanceTimersByTime(300); }); rerender({ value: 'updated2', delay: 500 }); - // Fast forward time by the remaining 200ms act(() => { vi.advanceTimersByTime(200); }); - // The debounced value should still be 'initial' because the timer was reset expect(result.current).toBe('initial'); - // Fast forward time by 300ms act(() => { vi.advanceTimersByTime(300); }); - // After the full delay, the debounced value should be 'updated2' expect(result.current).toBe('updated2'); }); afterEach(() => { - vi.clearAllTimers(); // Clear all timers after each test + vi.clearAllTimers(); }); });