Simplify and fix inefficiencies in contract search component (#756)

* Simplify and fix inefficiencies in contract search component

* Add react-dom types

* Add a clarifying comment

* Improve search per some feedback
This commit is contained in:
Marshall Polaris 2022-08-13 13:15:11 -07:00 committed by GitHub
parent 0a9df3ac6b
commit 0085ffcb0b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 43 additions and 52 deletions

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,6 @@ export function ContractSearch(props: {
const selectPill = (pill: string | undefined) => () => { const selectPill = (pill: string | undefined) => () => {
setPillFilter(pill) setPillFilter(pill)
setPage(0)
track('select search category', { category: pill ?? 'all' }) track('select search category', { category: pill ?? 'all' })
} }
@ -167,79 +167,62 @@ 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 performQuery = async (freshQuery?: boolean) => {
let wasMostRecentQuery = true const id = ++requestId.current
const algoliaIndex = query ? searchIndex : index const requestedPage = freshQuery ? 0 : pages.length
if (freshQuery || requestedPage < numPages) {
algoliaIndex const algoliaIndex = query ? searchIndex : index
.search(query, { const results = await algoliaIndex.search(query, {
facetFilters, facetFilters,
numericFilters, numericFilters,
page, page: requestedPage,
hitsPerPage: 20, hitsPerPage: 20,
}) })
.then((results) => { // if there's a more recent request, forget about this one
if (!wasMostRecentQuery) return if (id === requestId.current) {
const newPage = results.hits as any as Contract[]
if (page === 0) { // this spooky looking function is the easiest way to get react to
setHitsByPage({ // batch this and not do two renders. we can throw it out in react 18.
[0]: results.hits as any as Contract[], // see https://github.com/reactwg/react-18/discussions/21
}) unstable_batchedUpdates(() => {
} else { setNumPages(results.nbPages)
setHitsByPage((hitsByPage) => ({ if (freshQuery) {
...hitsByPage, setPages([newPage])
[page]: results.hits, } else {
})) setPages((pages) => [...pages, newPage])
} }
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] ?? []) performQuery(true)
.flat() }, [query, index, searchIndex, filter, JSON.stringify(facetFilters)])
const contracts = hits.filter( const contracts = pages
(c) => !additionalFilter?.excludeContractIds?.includes(c.id) .flat()
) .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)
} }
const selectFilter = (newFilter: filter) => { const selectFilter = (newFilter: filter) => {
if (newFilter === filter) return if (newFilter === filter) return
setFilter(newFilter) setFilter(newFilter)
setPage(0)
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)
track('select search sort', { sort: newSort }) track('select search sort', { sort: newSort })
} }
@ -345,8 +328,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={performQuery}
showTime={showTime} showTime={showTime}
onContractClick={onContractClick} onContractClick={onContractClick}
overrideGridClassName={overrideGridClassName} overrideGridClassName={overrideGridClassName}

View File

@ -67,6 +67,7 @@
"@types/lodash": "4.14.178", "@types/lodash": "4.14.178",
"@types/node": "16.11.11", "@types/node": "16.11.11",
"@types/react": "17.0.43", "@types/react": "17.0.43",
"@types/react-dom": "17.0.2",
"@types/string-similarity": "^4.0.0", "@types/string-similarity": "^4.0.0",
"autoprefixer": "10.2.6", "autoprefixer": "10.2.6",
"critters": "0.0.16", "critters": "0.0.16",

View File

@ -3386,6 +3386,13 @@
resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc" resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc"
integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw== integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==
"@types/react-dom@17.0.2":
version "17.0.2"
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-17.0.2.tgz#35654cf6c49ae162d5bc90843d5437dc38008d43"
integrity sha512-Icd9KEgdnFfJs39KyRyr0jQ7EKhq8U6CcHRMGAS45fp5qgUvxL3ujUCfWFttUK2UErqZNj97t9gsVPNAqcwoCg==
dependencies:
"@types/react" "*"
"@types/react-router-config@*": "@types/react-router-config@*":
version "5.0.6" version "5.0.6"
resolved "https://registry.yarnpkg.com/@types/react-router-config/-/react-router-config-5.0.6.tgz#87c5c57e72d241db900d9734512c50ccec062451" resolved "https://registry.yarnpkg.com/@types/react-router-config/-/react-router-config-5.0.6.tgz#87c5c57e72d241db900d9734512c50ccec062451"