From 1f42d783821f45a0c750544839ea2b6da2f663d3 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sat, 12 Mar 2022 15:50:53 -0500 Subject: [PATCH] Add a debounced state helper --- .../components/admin/users/UsersContainer.tsx | 41 ++++--------------- .../scripts/helpers/extractSearchFilters.ts | 2 +- .../scripts/plugins/useDebouncedState.ts | 12 ++++++ 3 files changed, 20 insertions(+), 35 deletions(-) create mode 100644 resources/scripts/plugins/useDebouncedState.ts diff --git a/resources/scripts/components/admin/users/UsersContainer.tsx b/resources/scripts/components/admin/users/UsersContainer.tsx index 5889c2e66..56231a8b2 100644 --- a/resources/scripts/components/admin/users/UsersContainer.tsx +++ b/resources/scripts/components/admin/users/UsersContainer.tsx @@ -1,5 +1,4 @@ -import React, { Fragment, useCallback, useEffect, useState } from 'react'; -import { QueryBuilderParams } from '@/api/http'; +import React, { Fragment, useEffect, useState } from 'react'; import { UUID } from '@/api/definitions'; import { User } from '@definitions/admin'; import { Transition } from '@/components/elements/transitions'; @@ -7,40 +6,17 @@ import { LockOpenIcon, PlusIcon, SupportIcon, TrashIcon } from '@heroicons/react import { Button } from '@/components/elements/button/index'; import { Checkbox, InputField } from '@/components/elements/inputs'; import UserTableRow from '@/components/admin/users/UserTableRow'; -import debounce from 'debounce'; import { useGetUsers } from '@/api/admin/users'; import TFootPaginated from '@/components/elements/table/TFootPaginated'; +import extractSearchFilters from '@/helpers/extractSearchFilters'; +import useDebouncedState from '@/plugins/useDebouncedState'; const filters = [ 'id', 'uuid', 'external_id', 'username', 'email' ] as const; -type Filters = typeof filters[number]; - -const extractFiltersFromString = (str: string, params: (keyof Filters)[]): QueryBuilderParams => { - const filters: Partial> = {}; - - const parts = str.split(' '); - for (const segment of parts) { - const [ filter, value ] = segment.split(':', 2); - // @ts-ignore - if (!filter || !value || !params.includes(filter)) { - continue; - } - - const key = filter as string; - filters[key] = [ ...(filters[key] || []), value ]; - } - - if (!Object.keys(filters).length) { - return { filters: { email: str } }; - } - - // @ts-ignore - return { filters }; -}; const UsersContainer = () => { - const [ search, setSearch ] = useState(''); + const [ search, setSearch ] = useDebouncedState('', 500); const [ selected, setSelected ] = useState([]); - const { data: users } = useGetUsers(extractFiltersFromString(search, filters as unknown as (keyof Filters)[])); + const { data: users } = useGetUsers(extractSearchFilters(search, filters)); useEffect(() => { document.title = 'Admin | Users'; @@ -55,10 +31,6 @@ const UsersContainer = () => { const selectAllChecked = users && users.items.length > 0 && selected.length > 0; const onSelectAll = () => setSelected((state) => state.length > 0 ? [] : users?.items.map(({ uuid }) => uuid) || []); - const setSearchTerm = useCallback(debounce((term: string) => { - setSearch(term); - }, 200), []); - return (
@@ -70,6 +42,7 @@ const UsersContainer = () => {
@@ -80,7 +53,7 @@ const UsersContainer = () => { name={'filter'} placeholder={'Begin typing to filter...'} className={'w-56 focus:w-96'} - onChange={e => setSearchTerm(e.currentTarget.value)} + onChange={e => setSearch(e.currentTarget.value)} />
0} duration={'duration-75'}> diff --git a/resources/scripts/helpers/extractSearchFilters.ts b/resources/scripts/helpers/extractSearchFilters.ts index 1497050b8..a603d81c4 100644 --- a/resources/scripts/helpers/extractSearchFilters.ts +++ b/resources/scripts/helpers/extractSearchFilters.ts @@ -3,7 +3,7 @@ import splitStringWhitespace from '@/helpers/splitStringWhitespace'; const extractSearchFilters = ( str: string, - params: T[], + params: Readonly, defaultFilter: D = '*' as D, ): QueryBuilderParams | QueryBuilderParams => { const filters: Map = new Map(); diff --git a/resources/scripts/plugins/useDebouncedState.ts b/resources/scripts/plugins/useDebouncedState.ts new file mode 100644 index 000000000..9ea4c1086 --- /dev/null +++ b/resources/scripts/plugins/useDebouncedState.ts @@ -0,0 +1,12 @@ +import { useState } from 'react'; +import { debounce } from 'debounce'; + +type DebounceFn = ((value: V) => void) & { clear: () => void }; + +export default (initial: S, interval?: number, immediate?: boolean): [ S, DebounceFn ] => { + const [ state, setState ] = useState(initial); + + const debouncedSetState = debounce((v: S) => setState(v), interval, immediate); + + return [ state, debouncedSetState ]; +};