Skip to content

Commit

Permalink
fix(filter): add useInfiniteDataQuery
Browse files Browse the repository at this point in the history
  • Loading branch information
Birkbjo committed Jan 29, 2024
1 parent 5c7acb4 commit 0fa3b91
Show file tree
Hide file tree
Showing 6 changed files with 125 additions and 58 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ interface SearchableSingleSelectPropTypes {
onFilterChange: OnFilterChange
onEndReached: () => void
onRetryClick: () => void
dense?: boolean
options: Option[]
placeholder: string
prefix?: string
Expand All @@ -68,6 +69,7 @@ interface SearchableSingleSelectPropTypes {
export const SearchableSingleSelect = ({
invalid,
error,
dense,
loading,
placeholder,
prefix,
Expand Down Expand Up @@ -130,6 +132,7 @@ export const SearchableSingleSelect = ({
prefix={prefix}
onBlur={onBlur}
onFocus={onFocus}
dense={dense}
>
<div className={classes.searchField}>
<div className={classes.searchInput}>
Expand Down
96 changes: 40 additions & 56 deletions src/components/sectionList/filters/filterSelectors/ModelFilter.tsx
Original file line number Diff line number Diff line change
@@ -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({
Expand Down Expand Up @@ -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,
Expand All @@ -69,7 +57,7 @@ export interface ModelSingleSelectProps {
onChange: ({ selected }: { selected: string | undefined }) => void
selected?: string
placeholder: string
query: Query
query: ResultQuery
}

export const ModelFilterSelect = ({
Expand All @@ -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<string | undefined>()
const pageRef = useRef(0)

const [initialQuery] = useState(() =>
createInitialOptionQuery(query.result.resource, selected)
Expand All @@ -101,31 +88,25 @@ export const ModelFilterSelect = ({
// the page with the selected option hasn't been reached yet or when
// filtering)
const [selectedOption, setSelectedOption] = useState<OptionResult>()

const optionsQueryResult = useDataQuery<PagedResult>(query)
const { refetch, data } = optionsQueryResult
const optionsQueryResult = useInfiniteDataQuery<OptionResult>(query)
// const optionsQueryResult = useDataQuery<PagedResult>(query)
const { refetch, data, incrementPage } = optionsQueryResult

const pager = data?.result.pager
const page = pager?.page || 0
const pageCount = pager?.pageCount || 0

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 ||
Expand All @@ -145,33 +126,36 @@ export const ModelFilterSelect = ({
})

return (
<SearchableSingleSelect
placeholder={placeholder}
prefix={placeholder}
showAllOption={true}
onChange={({ selected }) => {
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,
})
}}
/>
<div style={{ minWidth: 200 }}>
<SearchableSingleSelect
dense
placeholder={placeholder}
prefix={placeholder}
showAllOption={true}
onChange={({ selected }) => {
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,
})
}}
/>
</div>
)
}
Original file line number Diff line number Diff line change
@@ -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) => ({
Expand Down
1 change: 1 addition & 0 deletions src/lib/query/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './useInfiniteDataQuery'
73 changes: 73 additions & 0 deletions src/lib/query/useInfiniteDataQuery.ts
Original file line number Diff line number Diff line change
@@ -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<TData> = PagerObject & { [key: string]: TData[] }

type InfiniteQueryResult<TData> = WrapQueryResponse<PagedResponse<TData>>

type QueryOptions = Parameters<typeof useDataQuery>[1]
type InfiniteQueryOptions = QueryOptions & {
dataResultKey?: string
}
export const useInfiniteDataQuery = <TData>(
query: ResultQuery,
options?: InfiniteQueryOptions
) => {
const [allResult, setAllResult] = useState<TData[]>([])

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<InfiniteQueryResult<TData>>(
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,
}
}
6 changes: 6 additions & 0 deletions src/types/query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,14 @@ export type QueryResponse = ReturnType<typeof useDataQuery>

export type Query = Parameters<typeof useDataQuery>[0]

export type ResourceQuery = Query[keyof Query]

export type QueryRefetchFunction = QueryResponse['refetch']

export type WrapQueryResponse<T, S extends string = 'result'> = {
[K in S]: T
}

export type ResultQuery = {
result: ResourceQuery
}

0 comments on commit 0fa3b91

Please sign in to comment.