diff --git a/web/components/contract-search.tsx b/web/components/contract-search.tsx
index 98debc9f..54b30f3f 100644
--- a/web/components/contract-search.tsx
+++ b/web/components/contract-search.tsx
@@ -4,11 +4,7 @@ import { SearchOptions } from '@algolia/client-search'
import { Contract } from 'common/contract'
import { User } from 'common/user'
-import {
- QuerySortOptions,
- Sort,
- useQueryAndSortParams,
-} from '../hooks/use-sort-and-query-params'
+import { Sort, useQuery, useSort } from '../hooks/use-sort-and-query-params'
import {
ContractHighlightOptions,
ContractsGrid,
@@ -24,11 +20,25 @@ import ContractSearchFirestore from 'web/pages/contract-search-firestore'
import { useMemberGroups } from 'web/hooks/use-group'
import { NEW_USER_GROUP_SLUGS } from 'common/group'
import { PillButton } from './buttons/pill-button'
-import { sortBy } from 'lodash'
+import { debounce, sortBy } from 'lodash'
import { DEFAULT_CATEGORY_GROUPS } from 'common/categories'
import { Col } from './layout/col'
+import { safeLocalStorage } from 'web/lib/util/local'
import clsx from 'clsx'
+// TODO: this obviously doesn't work with SSR, common sense would suggest
+// that we should save things like this in cookies so the server has them
+
+const MARKETS_SORT = 'markets_sort'
+
+function setSavedSort(s: Sort) {
+ safeLocalStorage()?.setItem(MARKETS_SORT, s)
+}
+
+function getSavedSort() {
+ return safeLocalStorage()?.getItem(MARKETS_SORT) as Sort | null | undefined
+}
+
const searchClient = algoliasearch(
'GJQPAYENIF',
'75c28fc084a80e1129d427d470cf41a3'
@@ -47,7 +57,6 @@ const sortOptions = [
{ label: 'Close date', value: 'close-date' },
{ label: 'Resolve date', value: 'resolve-date' },
]
-export const DEFAULT_SORT = 'score'
type filter = 'personal' | 'open' | 'closed' | 'resolved' | 'all'
@@ -68,7 +77,8 @@ type AdditionalFilter = {
export function ContractSearch(props: {
user?: User | null
- querySortOptions?: { defaultFilter?: filter } & QuerySortOptions
+ defaultSort?: Sort
+ defaultFilter?: filter
additionalFilter?: AdditionalFilter
highlightOptions?: ContractHighlightOptions
onContractClick?: (contract: Contract) => void
@@ -79,10 +89,13 @@ export function ContractSearch(props: {
hideQuickBet?: boolean
}
headerClassName?: string
+ useQuerySortLocalStorage?: boolean
+ useQuerySortUrlParams?: boolean
}) {
const {
user,
- querySortOptions,
+ defaultSort,
+ defaultFilter,
additionalFilter,
onContractClick,
overrideGridClassName,
@@ -90,6 +103,8 @@ export function ContractSearch(props: {
cardHideOptions,
highlightOptions,
headerClassName,
+ useQuerySortLocalStorage,
+ useQuerySortUrlParams,
} = props
const [numPages, setNumPages] = useState(1)
@@ -132,31 +147,33 @@ export function ContractSearch(props: {
}
}
+ const onSearchParametersChanged = useRef(
+ debounce((params) => {
+ searchParameters.current = params
+ performQuery(true)
+ }, 100)
+ ).current
+
const contracts = pages
.flat()
.filter((c) => !additionalFilter?.excludeContractIds?.includes(c.id))
if (IS_PRIVATE_MANIFOLD || process.env.NEXT_PUBLIC_FIREBASE_EMULATE) {
- return (
-
- )
+ return
}
return (
{
- searchParameters.current = params
- performQuery(true)
- }}
+ onSearchParametersChanged={onSearchParametersChanged}
/>
void
- querySortOptions?: { defaultFilter?: filter } & QuerySortOptions
+ useQuerySortLocalStorage?: boolean
+ useQuerySortUrlParams?: boolean
user?: User | null
}) {
const {
className,
+ defaultSort,
+ defaultFilter,
additionalFilter,
hideOrderSelector,
onSearchParametersChanged,
- querySortOptions,
+ useQuerySortLocalStorage,
+ useQuerySortUrlParams,
user,
} = props
- const { query, setQuery, sort, setSort } =
- useQueryAndSortParams(querySortOptions)
+ const savedSort = useQuerySortLocalStorage ? getSavedSort() : null
+ const initialSort = savedSort ?? defaultSort ?? 'score'
+ const querySortOpts = { useUrl: !!useQuerySortUrlParams }
+ const [sort, setSort] = useSort(initialSort, querySortOpts)
+ const [query, setQuery] = useQuery('', querySortOpts)
+ const [filter, setFilter] = useState(defaultFilter ?? 'open')
+ const [pillFilter, setPillFilter] = useState(undefined)
+
+ useEffect(() => {
+ if (useQuerySortLocalStorage) {
+ setSavedSort(sort)
+ }
+ }, [sort])
const follows = useFollows(user?.id)
const memberGroups = (useMemberGroups(user?.id) ?? []).filter(
@@ -208,12 +242,6 @@ function ContractSearchControls(props: {
const pillGroups: { name: string; slug: string }[] =
memberPillGroups.length > 0 ? memberPillGroups : DEFAULT_CATEGORY_GROUPS
- const [filter, setFilter] = useState(
- querySortOptions?.defaultFilter ?? 'open'
- )
-
- const [pillFilter, setPillFilter] = useState(undefined)
-
const additionalFilters = [
additionalFilter?.creatorId
? `creatorId:${additionalFilter.creatorId}`
diff --git a/web/components/contract/contracts-grid.tsx b/web/components/contract/contracts-grid.tsx
index f62c3c85..05c66d56 100644
--- a/web/components/contract/contracts-grid.tsx
+++ b/web/components/contract/contracts-grid.tsx
@@ -106,11 +106,8 @@ export function CreatorContractsList(props: {
return (
c.id),
highlightClassName:
diff --git a/web/hooks/use-sort-and-query-params.tsx b/web/hooks/use-sort-and-query-params.tsx
index e917e4af..0a2834d0 100644
--- a/web/hooks/use-sort-and-query-params.tsx
+++ b/web/hooks/use-sort-and-query-params.tsx
@@ -1,9 +1,5 @@
-import { debounce } from 'lodash'
-import { useRouter } from 'next/router'
-import { useEffect, useMemo, useState } from 'react'
-import { DEFAULT_SORT } from 'web/components/contract-search'
-
-const MARKETS_SORT = 'markets_sort'
+import { useState } from 'react'
+import { NextRouter, useRouter } from 'next/router'
export type Sort =
| 'newest'
@@ -15,92 +11,55 @@ export type Sort =
| 'last-updated'
| 'score'
-export function getSavedSort() {
- // TODO: this obviously doesn't work with SSR, common sense would suggest
- // that we should save things like this in cookies so the server has them
- if (typeof window !== 'undefined') {
- return localStorage.getItem(MARKETS_SORT) as Sort | null
- } else {
- return null
+type UpdatedQueryParams = { [k: string]: string }
+type QuerySortOpts = { useUrl: boolean }
+
+function withURLParams(location: Location, params: UpdatedQueryParams) {
+ const newParams = new URLSearchParams(location.search)
+ for (const [k, v] of Object.entries(params)) {
+ if (!v) {
+ newParams.delete(k)
+ } else {
+ newParams.set(k, v)
+ }
}
+ const newUrl = new URL(location.href)
+ newUrl.search = newParams.toString()
+ return newUrl
}
-export interface QuerySortOptions {
- defaultSort?: Sort
- shouldLoadFromStorage?: boolean
- /** Use normal react state instead of url query string */
- disableQueryString?: boolean
+function updateURL(params: UpdatedQueryParams) {
+ // see relevant discussion here https://github.com/vercel/next.js/discussions/18072
+ const url = withURLParams(window.location, params).toString()
+ const updatedState = { ...window.history.state, as: url, url }
+ window.history.replaceState(updatedState, '', url)
}
-export function useQueryAndSortParams({
- defaultSort = DEFAULT_SORT,
- shouldLoadFromStorage = true,
- disableQueryString,
-}: QuerySortOptions = {}) {
+function getStringURLParam(router: NextRouter, k: string) {
+ const v = router.query[k]
+ return typeof v === 'string' ? v : null
+}
+
+export function useQuery(defaultQuery: string, opts?: QuerySortOpts) {
+ const useUrl = opts?.useUrl ?? false
const router = useRouter()
-
- const { s: sort, q: query } = router.query as {
- q?: string
- s?: Sort
- }
-
- const setSort = (sort: Sort | undefined) => {
- router.replace({ query: { ...router.query, s: sort } }, undefined, {
- shallow: true,
- })
- if (shouldLoadFromStorage) {
- localStorage.setItem(MARKETS_SORT, sort || '')
- }
- }
-
- const [queryState, setQueryState] = useState(query)
-
- useEffect(() => {
- setQueryState(query)
- }, [query])
-
- // Debounce router query update.
- const pushQuery = useMemo(
- () =>
- debounce((query: string | undefined) => {
- const queryObj = { ...router.query, q: query }
- if (!query) delete queryObj.q
- router.replace({ query: queryObj }, undefined, {
- shallow: true,
- })
- }, 100),
- [router]
- )
-
- const setQuery = (query: string | undefined) => {
- setQueryState(query)
- if (!disableQueryString) {
- pushQuery(query)
- }
- }
-
- useEffect(() => {
- // If there's no sort option, then set the one from localstorage
- if (router.isReady && !sort && shouldLoadFromStorage) {
- const localSort = localStorage.getItem(MARKETS_SORT) as Sort
- if (localSort && localSort !== defaultSort) {
- // Use replace to not break navigating back.
- router.replace(
- { query: { ...router.query, s: localSort } },
- undefined,
- { shallow: true }
- )
- }
- }
- })
-
- // use normal state if querydisableQueryString
- const [sortState, setSortState] = useState(defaultSort)
-
- return {
- sort: disableQueryString ? sortState : sort ?? defaultSort,
- query: queryState ?? '',
- setSort: disableQueryString ? setSortState : setSort,
- setQuery,
+ const initialQuery = useUrl ? getStringURLParam(router, 'q') : null
+ const [query, setQuery] = useState(initialQuery ?? defaultQuery)
+ if (!useUrl) {
+ return [query, setQuery] as const
+ } else {
+ return [query, (q: string) => (setQuery(q), updateURL({ q }))] as const
+ }
+}
+
+export function useSort(defaultSort: Sort, opts?: QuerySortOpts) {
+ const useUrl = opts?.useUrl ?? false
+ const router = useRouter()
+ const initialSort = useUrl ? (getStringURLParam(router, 's') as Sort) : null
+ const [sort, setSort] = useState(initialSort ?? defaultSort)
+ if (!useUrl) {
+ return [sort, setSort] as const
+ } else {
+ return [sort, (s: Sort) => (setSort(s), updateURL({ s }))] as const
}
}
diff --git a/web/pages/contract-search-firestore.tsx b/web/pages/contract-search-firestore.tsx
index f56c82d1..ec480269 100644
--- a/web/pages/contract-search-firestore.tsx
+++ b/web/pages/contract-search-firestore.tsx
@@ -3,16 +3,11 @@ import { searchInAny } from 'common/util/parse'
import { sortBy } from 'lodash'
import { ContractsGrid } from 'web/components/contract/contracts-grid'
import { useContracts } from 'web/hooks/use-contracts'
-import {
- QuerySortOptions,
- Sort,
- useQueryAndSortParams,
-} from 'web/hooks/use-sort-and-query-params'
+import { Sort, useQuery, useSort } from 'web/hooks/use-sort-and-query-params'
const MAX_CONTRACTS_RENDERED = 100
export default function ContractSearchFirestore(props: {
- querySortOptions?: QuerySortOptions
additionalFilter?: {
creatorId?: string
tag?: string
@@ -21,10 +16,9 @@ export default function ContractSearchFirestore(props: {
}
}) {
const contracts = useContracts()
- const { querySortOptions, additionalFilter } = props
-
- const { query, setQuery, sort, setSort } =
- useQueryAndSortParams(querySortOptions)
+ const { additionalFilter } = props
+ const [query, setQuery] = useQuery('', { useUrl: true })
+ const [sort, setSort] = useSort('score', { useUrl: true })
let matches = (contracts ?? []).filter((c) =>
searchInAny(
diff --git a/web/pages/group/[...slugs]/index.tsx b/web/pages/group/[...slugs]/index.tsx
index cd4b7344..c5255974 100644
--- a/web/pages/group/[...slugs]/index.tsx
+++ b/web/pages/group/[...slugs]/index.tsx
@@ -31,7 +31,6 @@ import { CreateQuestionButton } from 'web/components/create-question-button'
import React, { useState } from 'react'
import { LoadingIndicator } from 'web/components/loading-indicator'
import { Modal } from 'web/components/layout/modal'
-import { getSavedSort } from 'web/hooks/use-sort-and-query-params'
import { ChoicesToggleGroup } from 'web/components/choices-toggle-group'
import { toast } from 'react-hot-toast'
import { useCommentsOnGroup } from 'web/hooks/use-comments'
@@ -196,11 +195,8 @@ export default function GroupPage(props: {
const questionsTab = (
)
diff --git a/web/pages/home.tsx b/web/pages/home.tsx
index 839a08f3..b11c0cf9 100644
--- a/web/pages/home.tsx
+++ b/web/pages/home.tsx
@@ -4,8 +4,7 @@ import { PlusSmIcon } from '@heroicons/react/solid'
import { Page } from 'web/components/page'
import { Col } from 'web/components/layout/col'
-import { getSavedSort } from 'web/hooks/use-sort-and-query-params'
-import { ContractSearch, DEFAULT_SORT } from 'web/components/contract-search'
+import { ContractSearch } from 'web/components/contract-search'
import { Contract } from 'common/contract'
import { User } from 'common/user'
import { ContractPageContent } from './[username]/[contractSlug]'
@@ -35,10 +34,8 @@ const Home = (props: { auth: { user: User } }) => {
{
// Show contract without navigating to contract page.
setContract(c)
diff --git a/web/pages/tag/[tag].tsx b/web/pages/tag/[tag].tsx
index c1dce29e..f2554f49 100644
--- a/web/pages/tag/[tag].tsx
+++ b/web/pages/tag/[tag].tsx
@@ -15,11 +15,8 @@ export default function TagPage() {