From 58b11b4482aeee8229c74dbd8e001195fefc66b9 Mon Sep 17 00:00:00 2001 From: seatuna Date: Tue, 25 Mar 2025 00:23:02 -0400 Subject: [PATCH] feat: replace custom routing with react-instantsearch-router-nextjs --- components/search/bills/BillSearch.tsx | 18 ++++-- components/search/routingHelpers.ts | 36 ++++++++++++ .../search/testimony/TestimonySearch.tsx | 16 +++++- components/search/useRouting.tsx | 55 ------------------- package.json | 1 + yarn.lock | 55 +++++++++++++++++++ 6 files changed, 119 insertions(+), 62 deletions(-) create mode 100644 components/search/routingHelpers.ts delete mode 100644 components/search/useRouting.tsx diff --git a/components/search/bills/BillSearch.tsx b/components/search/bills/BillSearch.tsx index faadd018e..80cefa3ec 100644 --- a/components/search/bills/BillSearch.tsx +++ b/components/search/bills/BillSearch.tsx @@ -7,21 +7,23 @@ import { SearchBox, useInstantSearch } from "react-instantsearch" -import { currentGeneralCourt } from "functions/src/shared" +import { createInstantSearchRouterNext } from "react-instantsearch-router-nextjs" +import singletonRouter from "next/router" import styled from "styled-components" import TypesenseInstantSearchAdapter from "typesense-instantsearch-adapter" +import { currentGeneralCourt } from "functions/src/shared" import { Col, Container, Row, Spinner } from "../../bootstrap" import { NoResults } from "../NoResults" import { ResultCount } from "../ResultCount" import { SearchContainer } from "../SearchContainer" import { SearchErrorBoundary } from "../SearchErrorBoundary" -import { useRouting } from "../useRouting" import { BillHit } from "./BillHit" import { useBillRefinements } from "./useBillRefinements" import { SortBy, SortByWithConfigurationItem } from "../SortBy" import { getServerConfig, VirtualFilters } from "../common" import { useBillSort } from "./useBillSort" import { FC, useState } from "react" +import { pathToSearchState, searchStateToUrl } from "../routingHelpers" const searchClient = new TypesenseInstantSearchAdapter({ server: getServerConfig(), @@ -70,8 +72,16 @@ export const BillSearch = () => { } }} searchClient={searchClient} - routing={useRouting()} - future={{ preserveSharedStateOnUnmount: true }} + routing={{ + router: createInstantSearchRouterNext({ + singletonRouter, + routerOptions: { + cleanUrlOnDispose: false, + createURL: args => searchStateToUrl(args), + parseURL: args => pathToSearchState(args) + } + }) + }} > diff --git a/components/search/routingHelpers.ts b/components/search/routingHelpers.ts new file mode 100644 index 000000000..42e139b30 --- /dev/null +++ b/components/search/routingHelpers.ts @@ -0,0 +1,36 @@ +import { UiState } from "instantsearch.js" +import QueryString from "qs" + +export const searchStateToUrl = (createUrlArgs: { + location: Location + qsModule: typeof QueryString + routeState: UiState +}) => { + const { location, qsModule: qs, routeState: searchState } = createUrlArgs + const base = location.origin + location.pathname + + const flagQueries = Object.fromEntries( + Object.entries(qs.parse(window.location.search.slice(1))).filter( + ([key]) => !Object.keys(searchState).includes(key) + ) + ) + + const query = qs.stringify({ + ...searchState, + ...flagQueries + }) + + return query ? `${base}?${query}` : base +} + +export const pathToSearchState = (parseUrlArgs: { + location: Location + qsModule: typeof QueryString +}) => { + const { location, qsModule: qs } = parseUrlArgs + const path = location.href + + return ( + path.includes("?") ? qs.parse(path.substring(path.indexOf("?") + 1)) : {} + ) as UiState +} diff --git a/components/search/testimony/TestimonySearch.tsx b/components/search/testimony/TestimonySearch.tsx index e77b42645..f9fe19271 100644 --- a/components/search/testimony/TestimonySearch.tsx +++ b/components/search/testimony/TestimonySearch.tsx @@ -6,6 +6,8 @@ import { SearchBox, useInstantSearch } from "react-instantsearch" +import { createInstantSearchRouterNext } from "react-instantsearch-router-nextjs" +import singletonRouter from "next/router" import { StyledTabContent, StyledTabNav @@ -23,10 +25,10 @@ import { SearchContainer } from "../SearchContainer" import { SearchErrorBoundary } from "../SearchErrorBoundary" import { SortBy } from "../SortBy" import { getServerConfig, VirtualFilters } from "../common" -import { useRouting } from "../useRouting" import { TestimonyHit } from "./TestimonyHit" import { useTestimonyRefinements } from "./useTestimonyRefinements" import { FollowContext, OrgFollowStatus } from "components/shared/FollowContext" +import { pathToSearchState, searchStateToUrl } from "../routingHelpers" const searchClient = new TypesenseInstantSearchAdapter({ server: getServerConfig(), @@ -63,8 +65,16 @@ export const TestimonySearch = () => ( } }} searchClient={searchClient} - routing={useRouting()} - future={{ preserveSharedStateOnUnmount: true }} + routing={{ + router: createInstantSearchRouterNext({ + singletonRouter, + routerOptions: { + cleanUrlOnDispose: false, + createURL: args => searchStateToUrl(args), + parseURL: args => pathToSearchState(args) + } + }) + }} > diff --git a/components/search/useRouting.tsx b/components/search/useRouting.tsx deleted file mode 100644 index e4ef3b9fc..000000000 --- a/components/search/useRouting.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import { UiState } from "instantsearch.js" -import { RouterProps } from "instantsearch.js/es/middlewares" -import Router from "next/router" -import qs from "qs" -import { useMemo } from "react" - -const pathToSearchState = (path: string) => - (path.includes("?") - ? qs.parse(path.substring(path.indexOf("?") + 1)) - : {}) as UiState - -const searchStateToUrl = (searchState: UiState) => { - const base = window.location.pathname - - const flagQueries = Object.fromEntries( - Object.entries(qs.parse(window.location.search.slice(1))).filter( - ([key]) => !Object.keys(searchState).includes(key) - ) - ) - - const query = qs.stringify({ - ...searchState, - ...flagQueries - }) - - return query ? `${base}?${query}` : base -} - -export function useRouting(): RouterProps { - return useMemo(() => { - let disposed = false - return { - router: { - createURL: searchStateToUrl, - dispose() { - disposed = true - // Clear back listener - Router.beforePopState(() => true) - }, - onUpdate(callback) { - Router.beforePopState(({ url }) => { - callback(pathToSearchState(url)) - return true - }) - }, - read: () => pathToSearchState(window.location.href), - write(route) { - if (disposed) return - const url = searchStateToUrl(route) - Router.push(url, url, { shallow: true }) - } - } - } - }, []) -} diff --git a/package.json b/package.json index 872e34dc8..9a0077886 100644 --- a/package.json +++ b/package.json @@ -118,6 +118,7 @@ "react-i18next": "^13.2.2", "react-inlinesvg": "^3.0.1", "react-instantsearch": "^7.12.4", + "react-instantsearch-router-nextjs": "^7.15.5", "react-is": "^18.2.0", "react-markdown": "^8.0.4", "react-overlays": "^5.1.1", diff --git a/yarn.lock b/yarn.lock index 052e5d190..1057a4fea 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5252,6 +5252,13 @@ algoliasearch-helper@3.22.5: dependencies: "@algolia/events" "^4.0.1" +algoliasearch-helper@3.24.3: + version "3.24.3" + resolved "https://registry.yarnpkg.com/algoliasearch-helper/-/algoliasearch-helper-3.24.3.tgz#9a358c3110bcd912e79ef606a6e7bdd7725d22ee" + integrity sha512-3QKg5lzSfUiPN8Hn1ViHEGv6PjK7i4SFEDLzwlSzPO/4mVOsyos7B7/AsEtFQW5KHHPiCq6DyJl+mzg7CYlEgw== + dependencies: + "@algolia/events" "^4.0.1" + all-contributors-cli@^6.20.5: version "6.26.1" resolved "https://registry.yarnpkg.com/all-contributors-cli/-/all-contributors-cli-6.26.1.tgz#9f3358c9b9d0a7e66c8f84ffebf5a6432a859cae" @@ -10304,6 +10311,13 @@ install-artifact-from-github@^1.3.5: resolved "https://registry.yarnpkg.com/install-artifact-from-github/-/install-artifact-from-github-1.3.5.tgz#88c96fe40e5eb21d45586d564208c648a1dbf38d" integrity sha512-gZHC7f/cJgXz7MXlHFBxPVMsvIbev1OQN1uKQYKVJDydGNm9oYf9JstbU4Atnh/eSvk41WtEovoRm+8IF686xg== +instantsearch-ui-components@0.11.1: + version "0.11.1" + resolved "https://registry.yarnpkg.com/instantsearch-ui-components/-/instantsearch-ui-components-0.11.1.tgz#664ca03f657079946e459af72fa8d2674799c466" + integrity sha512-ZqUbJYYgObQ47J08ftXV1KNC1vdEoiD4/49qrkCdW46kRzLxLgYXJGuEuk48DQwK4aBtIoccgTyfbMGfcqNjxg== + dependencies: + "@babel/runtime" "^7.1.2" + instantsearch-ui-components@0.9.0: version "0.9.0" resolved "https://registry.yarnpkg.com/instantsearch-ui-components/-/instantsearch-ui-components-0.9.0.tgz#f7ae71fe623d18eff32b73071749f31826cb7b89" @@ -10334,6 +10348,24 @@ instantsearch.js@4.74.2: qs "^6.5.1 < 6.10" search-insights "^2.15.0" +instantsearch.js@4.78.1: + version "4.78.1" + resolved "https://registry.yarnpkg.com/instantsearch.js/-/instantsearch.js-4.78.1.tgz#cee799b920ba08c7c4e5af5ba591b86a1d80af1d" + integrity sha512-nDTWQ6DUxYzBZfkSxb/QJsYMZPPU8SGlGurn9147ABvA5Eumtxmk3Qy55EBMl0VxKVltGy3axAYMRB/gKIIHkg== + dependencies: + "@algolia/events" "^4.0.1" + "@types/dom-speech-recognition" "^0.0.1" + "@types/google.maps" "^3.55.12" + "@types/hogan.js" "^3.0.0" + "@types/qs" "^6.5.3" + algoliasearch-helper "3.24.3" + hogan.js "^3.0.2" + htm "^3.0.0" + instantsearch-ui-components "0.11.1" + preact "^10.10.0" + qs "^6.5.1 < 6.10" + search-insights "^2.17.2" + instantsearch.js@^4.43.0: version "4.62.0" resolved "https://registry.yarnpkg.com/instantsearch.js/-/instantsearch.js-4.62.0.tgz#68577f4f04866728f22441cbc7464c544678d342" @@ -14646,6 +14678,24 @@ react-instantsearch-core@7.13.2: instantsearch.js "4.74.2" use-sync-external-store "^1.0.0" +react-instantsearch-core@7.15.5: + version "7.15.5" + resolved "https://registry.yarnpkg.com/react-instantsearch-core/-/react-instantsearch-core-7.15.5.tgz#65d1edc440de8dc73d55230d13af8cbcf1724221" + integrity sha512-SFxiwwMf0f5F/8U0Y4ullvQ7bZtbYE516UOJbxaHhjV8yY0i8c22K4lrBFrYbxVRT7QAcp2wLGHiB7r/lD7eRA== + dependencies: + "@babel/runtime" "^7.1.2" + algoliasearch-helper "3.24.3" + instantsearch.js "4.78.1" + use-sync-external-store "^1.0.0" + +react-instantsearch-router-nextjs@^7.15.5: + version "7.15.5" + resolved "https://registry.yarnpkg.com/react-instantsearch-router-nextjs/-/react-instantsearch-router-nextjs-7.15.5.tgz#a8b13bc5ad9bd8c5a689d48f2714eab6bed2514f" + integrity sha512-kn325Nl6QkZlkSuOXwKUOb56QkSsKes9XQdBLythKt2oZzzAfcaXSYzYsFEyj96cMYDLkRHhHvLhdyq4C8Xezg== + dependencies: + instantsearch.js "4.78.1" + react-instantsearch-core "7.15.5" + react-instantsearch@^7.12.4: version "7.13.2" resolved "https://registry.yarnpkg.com/react-instantsearch/-/react-instantsearch-7.13.2.tgz#db84d04bd399596fb0078625bc75a6abc65e4bc6" @@ -15487,6 +15537,11 @@ search-insights@^2.15.0: resolved "https://registry.yarnpkg.com/search-insights/-/search-insights-2.17.2.tgz#d13b2cabd44e15ade8f85f1c3b65c8c02138629a" integrity sha512-zFNpOpUO+tY2D85KrxJ+aqwnIfdEGi06UH2+xEb+Bp9Mwznmauqc9djbnBibJO5mpfUPPa8st6Sx65+vbeO45g== +search-insights@^2.17.2: + version "2.17.3" + resolved "https://registry.yarnpkg.com/search-insights/-/search-insights-2.17.3.tgz#8faea5d20507bf348caba0724e5386862847b661" + integrity sha512-RQPdCYTa8A68uM2jwxoY842xDhvx3E5LFL1LxvxCNMev4o5mLuokczhzjAgGwUZBAmOKZknArSxLKmXtIi2AxQ== + search-insights@^2.6.0: version "2.11.0" resolved "https://registry.yarnpkg.com/search-insights/-/search-insights-2.11.0.tgz#0512ae3b801fed5ff3a2ae82840bf20ba29d82e5"