Simplify and fix inefficiencies in contract search component

This commit is contained in:
Marshall Polaris 2022-08-13 01:39:58 -07:00
parent 0a9df3ac6b
commit c969040334

View File

@ -13,7 +13,8 @@ import {
ContractsGrid, ContractsGrid,
} from './contract/contracts-grid' } from './contract/contracts-grid'
import { Row } from './layout/row' import { Row } from './layout/row'
import { useEffect, useMemo, useState } from 'react' import { useEffect, useRef, useMemo, useState } from 'react'
import { unstable_batchedUpdates } from 'react-dom'
import { ENV, IS_PRIVATE_MANIFOLD } from 'common/envs/constants' import { ENV, IS_PRIVATE_MANIFOLD } from 'common/envs/constants'
import { useFollows } from 'web/hooks/use-follows' import { useFollows } from 'web/hooks/use-follows'
import { track, trackCallback } from 'web/lib/service/analytics' import { track, trackCallback } from 'web/lib/service/analytics'
@ -21,7 +22,7 @@ import ContractSearchFirestore from 'web/pages/contract-search-firestore'
import { useMemberGroups } from 'web/hooks/use-group' import { useMemberGroups } from 'web/hooks/use-group'
import { Group, NEW_USER_GROUP_SLUGS } from 'common/group' import { Group, NEW_USER_GROUP_SLUGS } from 'common/group'
import { PillButton } from './buttons/pill-button' import { PillButton } from './buttons/pill-button'
import { range, sortBy } from 'lodash' import { sortBy } from 'lodash'
import { DEFAULT_CATEGORY_GROUPS } from 'common/categories' import { DEFAULT_CATEGORY_GROUPS } from 'common/categories'
import { Col } from './layout/col' import { Col } from './layout/col'
import clsx from 'clsx' import clsx from 'clsx'
@ -111,7 +112,8 @@ export function ContractSearch(props: {
const selectPill = (pill: string | undefined) => () => { const selectPill = (pill: string | undefined) => () => {
setPillFilter(pill) setPillFilter(pill)
setPage(0) setPages([])
setNumPages(1)
track('select search category', { category: pill ?? 'all' }) track('select search category', { category: pill ?? 'all' })
} }
@ -167,80 +169,65 @@ export function ContractSearch(props: {
[searchIndexName] [searchIndexName]
) )
const [page, setPage] = useState(0)
const [numPages, setNumPages] = useState(1) const [numPages, setNumPages] = useState(1)
const [hitsByPage, setHitsByPage] = useState<{ [page: string]: Contract[] }>( const [pages, setPages] = useState<Contract[][]>([])
{} const requestId = useRef(0)
)
useEffect(() => { const queryNextPage = async () => {
let wasMostRecentQuery = true const id = ++requestId.current
if (pages.length < numPages) {
const algoliaIndex = query ? searchIndex : index const algoliaIndex = query ? searchIndex : index
const results = await algoliaIndex.search(query, {
algoliaIndex
.search(query, {
facetFilters, facetFilters,
numericFilters, numericFilters,
page, page: pages.length,
hitsPerPage: 20, hitsPerPage: 20,
}) })
.then((results) => { // if there's a more recent request, forget about this one
if (!wasMostRecentQuery) return if (id === requestId.current) {
// this spooky looking function is the easiest way to get react to
if (page === 0) { // batch this and not do two renders. we can throw it out in react 18.
setHitsByPage({ // see https://github.com/reactwg/react-18/discussions/21
[0]: results.hits as any as Contract[], unstable_batchedUpdates(() => {
}) setPages((pages) => [...pages, results.hits as any as Contract[]])
} else {
setHitsByPage((hitsByPage) => ({
...hitsByPage,
[page]: results.hits,
}))
}
setNumPages(results.nbPages) setNumPages(results.nbPages)
}) })
return () => {
wasMostRecentQuery = false
} }
// Note numeric filters are unique based on current time, so can't compare }
// them by value.
}, [query, page, index, searchIndex, JSON.stringify(facetFilters), filter])
const loadMore = () => {
if (page >= numPages - 1) return
const haveLoadedCurrentPage = hitsByPage[page]
if (haveLoadedCurrentPage) setPage(page + 1)
} }
const hits = range(0, page + 1) useEffect(() => {
.map((p) => hitsByPage[p] ?? []) if (pages.length === 0) {
queryNextPage()
}
}, [pages])
const contracts = pages
.flat() .flat()
.filter((c) => !additionalFilter?.excludeContractIds?.includes(c.id))
const contracts = hits.filter(
(c) => !additionalFilter?.excludeContractIds?.includes(c.id)
)
const showTime = const showTime =
sort === 'close-date' || sort === 'resolve-date' ? sort : undefined sort === 'close-date' || sort === 'resolve-date' ? sort : undefined
const updateQuery = (newQuery: string) => { const updateQuery = (newQuery: string) => {
setQuery(newQuery) setQuery(newQuery)
setPage(0) setPages([])
setNumPages(1)
} }
const selectFilter = (newFilter: filter) => { const selectFilter = (newFilter: filter) => {
if (newFilter === filter) return if (newFilter === filter) return
setFilter(newFilter) setFilter(newFilter)
setPage(0) setPages([])
setNumPages(1)
track('select search filter', { filter: newFilter }) track('select search filter', { filter: newFilter })
} }
const selectSort = (newSort: Sort) => { const selectSort = (newSort: Sort) => {
if (newSort === sort) return if (newSort === sort) return
setPage(0)
setSort(newSort) setSort(newSort)
setPages([])
setNumPages(1)
track('select search sort', { sort: newSort }) track('select search sort', { sort: newSort })
} }
@ -345,8 +332,8 @@ export function ContractSearch(props: {
<>You're not following anyone, nor in any of your own groups yet.</> <>You're not following anyone, nor in any of your own groups yet.</>
) : ( ) : (
<ContractsGrid <ContractsGrid
contracts={hitsByPage[0] === undefined ? undefined : contracts} contracts={pages.length === 0 ? undefined : contracts}
loadMore={loadMore} loadMore={queryNextPage}
showTime={showTime} showTime={showTime}
onContractClick={onContractClick} onContractClick={onContractClick}
overrideGridClassName={overrideGridClassName} overrideGridClassName={overrideGridClassName}