diff --git a/src/components/SearchableSingleSelect/SearchableSingleSelect.tsx b/src/components/SearchableSingleSelect/SearchableSingleSelect.tsx index 26894a26..1a3a1629 100644 --- a/src/components/SearchableSingleSelect/SearchableSingleSelect.tsx +++ b/src/components/SearchableSingleSelect/SearchableSingleSelect.tsx @@ -52,6 +52,7 @@ interface SearchableSingleSelectPropTypes { onFilterChange: OnFilterChange onEndReached: () => void onRetryClick: () => void + dense?: boolean options: Option[] placeholder: string prefix?: string @@ -68,6 +69,7 @@ interface SearchableSingleSelectPropTypes { export const SearchableSingleSelect = ({ invalid, error, + dense, loading, placeholder, prefix, @@ -130,6 +132,7 @@ export const SearchableSingleSelect = ({ prefix={prefix} onBlur={onBlur} onFocus={onFocus} + dense={dense} >
diff --git a/src/components/sectionList/filters/filterSelectors/ModelFilter.tsx b/src/components/sectionList/filters/filterSelectors/ModelFilter.tsx index eff0b634..c2f14d0a 100644 --- a/src/components/sectionList/filters/filterSelectors/ModelFilter.tsx +++ b/src/components/sectionList/filters/filterSelectors/ModelFilter.tsx @@ -1,7 +1,7 @@ import { useDataQuery } from '@dhis2/app-runtime' import React, { useCallback, useRef, useState } from 'react' -import type { Query } from '../../../../types' -import { Pager } from '../../../../types/generated' +import { useInfiniteDataQuery } from '../../../../lib/query' +import type { ResultQuery } from '../../../../types' import { Option, SearchableSingleSelect } from '../../../SearchableSingleSelect' function computeDisplayOptions({ @@ -34,27 +34,15 @@ function computeDisplayOptions({ })) } -type ModelQuery = { - result: Query[keyof Query] -} - type OptionResult = { id: string displayName: string } -type OptionsResult = { - result: { - pager: Pager - } & { [key: string]: OptionResult[] } -} - -type PagedResult = { pager: Pager } & OptionsResult - const createInitialOptionQuery = ( resource: string, selected?: string -): ModelQuery => ({ +): ResultQuery => ({ result: { resource: resource, id: selected, @@ -69,7 +57,7 @@ export interface ModelSingleSelectProps { onChange: ({ selected }: { selected: string | undefined }) => void selected?: string placeholder: string - query: Query + query: ResultQuery } export const ModelFilterSelect = ({ @@ -82,7 +70,6 @@ export const ModelFilterSelect = ({ // We're using this value only when imperatively calling `refetch`, // nothing that depends on the render-cycle depends on this value const filterRef = useRef() - const pageRef = useRef(0) const [initialQuery] = useState(() => createInitialOptionQuery(query.result.resource, selected) @@ -101,9 +88,9 @@ export const ModelFilterSelect = ({ // the page with the selected option hasn't been reached yet or when // filtering) const [selectedOption, setSelectedOption] = useState() - - const optionsQueryResult = useDataQuery(query) - const { refetch, data } = optionsQueryResult + const optionsQueryResult = useInfiniteDataQuery(query) + // const optionsQueryResult = useDataQuery(query) + const { refetch, data, incrementPage } = optionsQueryResult const pager = data?.result.pager const page = pager?.page || 0 @@ -111,21 +98,15 @@ export const ModelFilterSelect = ({ const refetchWithFilter = useCallback( ({ value }: { value: string }) => { - pageRef.current = 1 filterRef.current = value ? `displayName:ilike:${value}` : undefined refetch({ - page: pageRef.current, - filter: value ? filterRef.current : undefined, + page: 1, + filter: filterRef.current, }) }, [refetch] ) - const incrementPage = useCallback(() => { - pageRef.current = page + 1 - refetch({ page: pageRef.current, filter: filterRef.current }) - }, [refetch, page]) - const loading = optionsQueryResult.fetching || optionsQueryResult.loading || @@ -145,33 +126,36 @@ export const ModelFilterSelect = ({ }) return ( - { - if (selected === selectedOption?.id) { - setSelectedOption(undefined) - } else { - const option = options.find(({ id }) => id === selected) - setSelectedOption(option) - } - - onChange({ selected: selected }) - }} - onEndReached={incrementPage} - options={displayOptions} - selected={selected} - showEndLoader={!loading && page < pageCount} - onFilterChange={refetchWithFilter} - loading={loading} - error={error} - onRetryClick={() => { - refetch({ - page: pageRef.current, - filter: filterRef.current, - }) - }} - /> +
+ { + if (selected === selectedOption?.id) { + setSelectedOption(undefined) + } else { + const option = options.find(({ id }) => id === selected) + setSelectedOption(option) + } + + onChange({ selected: selected }) + }} + onEndReached={incrementPage} + options={displayOptions} + selected={selected} + showEndLoader={!loading && page < pageCount} + onFilterChange={refetchWithFilter} + loading={loading} + error={error} + onRetryClick={() => { + refetch({ + page: page, + filter: filterRef.current, + }) + }} + /> +
) } diff --git a/src/components/sectionList/filters/filterSelectors/createdFilterDataQuery.ts b/src/components/sectionList/filters/filterSelectors/createdFilterDataQuery.ts index f527c3ce..108c66ce 100644 --- a/src/components/sectionList/filters/filterSelectors/createdFilterDataQuery.ts +++ b/src/components/sectionList/filters/filterSelectors/createdFilterDataQuery.ts @@ -1,6 +1,6 @@ -import { Query } from '../../../../types' +import { ResultQuery } from '../../../../types' -export const createFilterDataQuery = (resource: string): Query => ({ +export const createFilterDataQuery = (resource: string): ResultQuery => ({ result: { resource: resource, params: (params) => ({ diff --git a/src/lib/query/index.ts b/src/lib/query/index.ts new file mode 100644 index 00000000..572da64c --- /dev/null +++ b/src/lib/query/index.ts @@ -0,0 +1 @@ +export * from './useInfiniteDataQuery' diff --git a/src/lib/query/useInfiniteDataQuery.ts b/src/lib/query/useInfiniteDataQuery.ts new file mode 100644 index 00000000..41ff629f --- /dev/null +++ b/src/lib/query/useInfiniteDataQuery.ts @@ -0,0 +1,73 @@ +import { useDataQuery } from '@dhis2/app-runtime' +import { useCallback, useEffect, useMemo, useState } from 'react' +import { ResultQuery, WrapQueryResponse } from '../../types' +import { Pager } from '../../types/generated' + +type PagerObject = { + pager: Pager +} + +type PagedResponse = PagerObject & { [key: string]: TData[] } + +type InfiniteQueryResult = WrapQueryResponse> + +type QueryOptions = Parameters[1] +type InfiniteQueryOptions = QueryOptions & { + dataResultKey?: string +} +export const useInfiniteDataQuery = ( + query: ResultQuery, + options?: InfiniteQueryOptions +) => { + const [allResult, setAllResult] = useState([]) + + let queryOptions: QueryOptions = undefined + let dataKey = query.result.resource + if (options) { + const { dataResultKey, ...opts } = options + dataKey = dataResultKey || query.result.resource + queryOptions = opts + } + + const queryResult = useDataQuery>( + query, + queryOptions + ) + const { refetch, data } = queryResult + + const pager = data?.result.pager + const page = pager?.page || 0 + const pageCount = pager?.pageCount || 0 + + useEffect(() => { + const result = data?.result[dataKey] + if (result) { + setAllResult((prev) => { + const pager = data.result.pager + if (pager.page === 1) { + return data.result[dataKey] + } + return [...prev, ...data.result[dataKey]] + }) + } + }, [data, dataKey, setAllResult]) + + const incrementPage = useCallback(() => { + refetch({ page: page + 1 }) + }, [refetch, page]) + + const newData = useMemo(() => { + return { + result: { + ...data?.result, + [dataKey]: allResult, + }, + } + }, [allResult, data, dataKey]) + return { + ...queryResult, + data: newData, + incrementPage, + hasNextPage: page < pageCount, + } +} diff --git a/src/types/query.ts b/src/types/query.ts index 033fa358..533dcf2e 100644 --- a/src/types/query.ts +++ b/src/types/query.ts @@ -4,8 +4,14 @@ export type QueryResponse = ReturnType export type Query = Parameters[0] +export type ResourceQuery = Query[keyof Query] + export type QueryRefetchFunction = QueryResponse['refetch'] export type WrapQueryResponse = { [K in S]: T } + +export type ResultQuery = { + result: ResourceQuery +}