diff --git a/examples/with-algolia-react-instantsearch/components/Search.tsx b/examples/with-algolia-react-instantsearch/components/Search.tsx
new file mode 100644
index 000000000000000..b508ee5740675fd
--- /dev/null
+++ b/examples/with-algolia-react-instantsearch/components/Search.tsx
@@ -0,0 +1,56 @@
+import {
+ RefinementList,
+ SearchBox,
+ Hits,
+ Configure,
+ Highlight,
+ Pagination,
+ InstantSearch,
+} from 'react-instantsearch-dom'
+import type { InstantSearchProps } from 'react-instantsearch-dom'
+
+const HitComponent = ({ hit }: any) => (
+
+
+
+
+
+
+
+
+
+ - ${hit.price}
+ - {hit.rating} stars
+
+
+
+
+
+
+
+
+
+)
+
+export function Search(props: InstantSearchProps) {
+ return (
+
+
+
+ React InstantSearch + Next.js
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/examples/with-algolia-react-instantsearch/components/app.js b/examples/with-algolia-react-instantsearch/components/app.js
deleted file mode 100644
index 1ea860dd9751efb..000000000000000
--- a/examples/with-algolia-react-instantsearch/components/app.js
+++ /dev/null
@@ -1,81 +0,0 @@
-import React from 'react'
-import PropTypes from 'prop-types'
-import {
- RefinementList,
- SearchBox,
- Hits,
- Configure,
- Highlight,
- Pagination,
- InstantSearch,
-} from 'react-instantsearch-dom'
-import { indexName, searchClient } from './instantsearch'
-
-const HitComponent = ({ hit }) => (
-
-
-
-
-
-
-
-
-
- - ${hit.price}
- - {hit.rating} stars
-
-
-
-
-
-
-
-
-
-)
-
-HitComponent.propTypes = {
- hit: PropTypes.object,
-}
-
-export default class App extends React.Component {
- static propTypes = {
- searchState: PropTypes.object,
- resultsState: PropTypes.oneOfType([PropTypes.object, PropTypes.array]),
- onSearchStateChange: PropTypes.func,
- }
-
- render() {
- return (
-
-
-
- React InstantSearch + Next.Js
-
-
-
-
-
-
-
-
- )
- }
-}
diff --git a/examples/with-algolia-react-instantsearch/components/head.js b/examples/with-algolia-react-instantsearch/components/head.js
deleted file mode 100644
index 59c6ef9b3e9bc62..000000000000000
--- a/examples/with-algolia-react-instantsearch/components/head.js
+++ /dev/null
@@ -1,48 +0,0 @@
-import NextHead from 'next/head'
-import { string } from 'prop-types'
-import React from 'react'
-
-const defaultDescription = ''
-const defaultOGURL = ''
-const defaultOGImage = ''
-
-export const Head = (props) => (
-
-
- {props.title || ''}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-)
-
-Head.propTypes = {
- title: string,
- description: string,
- url: string,
- ogImage: string,
-}
-
-export default Head
diff --git a/examples/with-algolia-react-instantsearch/components/index.js b/examples/with-algolia-react-instantsearch/components/index.js
deleted file mode 100644
index ce4b600e6d79bb0..000000000000000
--- a/examples/with-algolia-react-instantsearch/components/index.js
+++ /dev/null
@@ -1,3 +0,0 @@
-export * from './head'
-export { default as App } from './app'
-export { findResultsState } from './instantsearch'
diff --git a/examples/with-algolia-react-instantsearch/components/instantsearch.js b/examples/with-algolia-react-instantsearch/components/instantsearch.js
deleted file mode 100644
index 48c0eab95a84d4b..000000000000000
--- a/examples/with-algolia-react-instantsearch/components/instantsearch.js
+++ /dev/null
@@ -1,13 +0,0 @@
-import { findResultsState } from 'react-instantsearch-dom/server'
-import algoliasearch from 'algoliasearch/lite'
-
-const indexName = 'instant_search'
-
-// Keys are supplied from Algolia's instant search example
-// https://github.com/algolia/react-instantsearch
-const searchClient = algoliasearch(
- 'latency',
- '6be0576ff61c053d5f9a3225e2a90f76'
-)
-
-export { findResultsState, indexName, searchClient }
diff --git a/examples/with-algolia-react-instantsearch/next.config.js b/examples/with-algolia-react-instantsearch/next.config.js
deleted file mode 100644
index f960da55873849d..000000000000000
--- a/examples/with-algolia-react-instantsearch/next.config.js
+++ /dev/null
@@ -1,17 +0,0 @@
-module.exports = {
- webpack: (config) => {
- // Fixes npm packages that depend on `fs` module
- config.node = {
- fs: 'empty',
- }
- config.module.rules.push({
- test: /\.css$/,
- loader: ['style-loader', 'css-loader'],
- })
- if (config.resolve.alias) {
- delete config.resolve.alias.react
- delete config.resolve.alias['react-dom']
- }
- return config
- },
-}
diff --git a/examples/with-algolia-react-instantsearch/package.json b/examples/with-algolia-react-instantsearch/package.json
index 8d7cf559d3e3210..9743995a10a6849 100644
--- a/examples/with-algolia-react-instantsearch/package.json
+++ b/examples/with-algolia-react-instantsearch/package.json
@@ -2,19 +2,25 @@
"private": true,
"scripts": {
"dev": "next",
- "build": "cross-env NODE_ENV=development next build",
+ "build": "next build",
"start": "next start"
},
"dependencies": {
- "algoliasearch": "4.3.0",
- "cross-env": "^7.0.2",
- "css-loader": "1.0.0",
+ "algoliasearch": "^4.14.2",
+ "instantsearch.css": "^7.4.5",
"next": "latest",
- "prop-types": "^15.5.10",
- "qs": "^6.4.0",
+ "qs": "^6.11.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
- "react-instantsearch-dom": "6.6.0",
- "style-loader": "^0.17.0"
+ "react-instantsearch-dom": "^6.38.0"
+ },
+ "devDependencies": {
+ "@types/node": "^18.11.9",
+ "@types/qs": "^6.9.7",
+ "@types/react": "^18.0.25",
+ "@types/react-dom": "^18.0.8",
+ "@types/react-instantsearch-core": "^6.26.2",
+ "@types/react-instantsearch-dom": "^6.12.3",
+ "typescript": "^4.8.4"
}
}
diff --git a/examples/with-algolia-react-instantsearch/pages/_app.tsx b/examples/with-algolia-react-instantsearch/pages/_app.tsx
new file mode 100644
index 000000000000000..e3f0528e6ce663e
--- /dev/null
+++ b/examples/with-algolia-react-instantsearch/pages/_app.tsx
@@ -0,0 +1,7 @@
+import type { AppProps } from 'next/app'
+import 'instantsearch.css/themes/algolia.css'
+import '../styles/global.css'
+
+export default function MyApp({ Component, pageProps }: AppProps) {
+ return
+}
diff --git a/examples/with-algolia-react-instantsearch/pages/index.js b/examples/with-algolia-react-instantsearch/pages/index.js
deleted file mode 100644
index 2a12a345561a796..000000000000000
--- a/examples/with-algolia-react-instantsearch/pages/index.js
+++ /dev/null
@@ -1,60 +0,0 @@
-import { Head, App } from '../components'
-import {
- findResultsState,
- indexName,
- searchClient,
-} from '../components/instantsearch'
-import { Component } from 'react'
-import Router from 'next/router'
-import qs from 'qs'
-
-const updateAfter = 700
-
-const searchStateToUrl = (searchState) =>
- searchState ? `${window.location.pathname}?${qs.stringify(searchState)}` : ''
-
-export default class Home extends Component {
- constructor(props) {
- super(props)
- this.onSearchStateChange = this.onSearchStateChange.bind(this)
- }
-
- onSearchStateChange = (searchState) => {
- clearTimeout(this.debouncedSetState)
- this.debouncedSetState = setTimeout(() => {
- const href = searchStateToUrl(searchState)
- Router.push(href, href, {
- shallow: true,
- })
- }, updateAfter)
- this.setState({ searchState })
- }
-
- async componentDidMount() {
- this.setState({
- searchState: qs.parse(window.location.search.slice(1)),
- resultsState: await findResultsState(App, { indexName, searchClient }),
- })
- }
-
- UNSAFE_componentWillReceiveProps() {
- this.setState({ searchState: qs.parse(window.location.search.slice(1)) })
- }
-
- render() {
- return (
-
-
-
- {this.state && this.state.resultsState && this.state.searchState && (
-
- )}
-
-
- )
- }
-}
diff --git a/examples/with-algolia-react-instantsearch/pages/index.tsx b/examples/with-algolia-react-instantsearch/pages/index.tsx
new file mode 100644
index 000000000000000..da4817d5169bc52
--- /dev/null
+++ b/examples/with-algolia-react-instantsearch/pages/index.tsx
@@ -0,0 +1,84 @@
+import type { InferGetServerSidePropsType, GetServerSideProps } from 'next'
+import type { SearchState } from 'react-instantsearch-core'
+import { useState, useEffect, useRef } from 'react'
+import { useRouter } from 'next/router'
+import algoliasearch from 'algoliasearch/lite'
+import { findResultsState } from 'react-instantsearch-dom/server'
+import { Search } from '../components/Search'
+import { createURL, searchStateToURL, pathToSearchState } from '../utils'
+
+// Demo key provided by https://github.com/algolia/react-instantsearch
+const searchClient = algoliasearch(
+ 'latency',
+ '6be0576ff61c053d5f9a3225e2a90f76'
+)
+
+const defaultProps = {
+ searchClient,
+ indexName: 'instant_search',
+}
+
+export default function Page({
+ resultsState,
+ searchState: initialState,
+}: InferGetServerSidePropsType) {
+ const router = useRouter()
+ const debouncedSetState = useRef()
+ const [searchState, setSearchState] = useState(initialState)
+
+ const onSearchStateChange = (state: SearchState) => {
+ clearTimeout(debouncedSetState.current)
+ ;(debouncedSetState as any).current = setTimeout(() => {
+ const href = searchStateToURL(state)
+
+ router.push(href, href, { shallow: true })
+ }, 700)
+
+ setSearchState(state)
+ }
+
+ useEffect(() => {
+ if (router) {
+ router.beforePopState((state: SearchState) => {
+ const { url } = state
+ setSearchState(pathToSearchState(url))
+
+ return true
+ })
+ }
+ }, [router])
+
+ return (
+
+ )
+}
+
+interface PageProps {
+ searchState: SearchState
+ resultsState: unknown
+}
+
+export const getServerSideProps: GetServerSideProps = async ({
+ resolvedUrl,
+}) => {
+ const searchState = pathToSearchState(resolvedUrl)
+ const resultsState = await findResultsState(Search, {
+ ...defaultProps,
+ searchState,
+ })
+
+ // Pre-serialize `findResultsState` object return so Next.js' serialization checks pass
+ // https://github.com/vercel/next.js/issues/11993
+ return {
+ props: {
+ resultsState: JSON.parse(JSON.stringify(resultsState)),
+ searchState,
+ },
+ }
+}
diff --git a/examples/with-algolia-react-instantsearch/public/favicon.ico b/examples/with-algolia-react-instantsearch/public/favicon.ico
deleted file mode 100644
index 4965832f2c9b060..000000000000000
Binary files a/examples/with-algolia-react-instantsearch/public/favicon.ico and /dev/null differ
diff --git a/examples/with-algolia-react-instantsearch/public/static/instantsearch.css b/examples/with-algolia-react-instantsearch/styles/global.css
similarity index 74%
rename from examples/with-algolia-react-instantsearch/public/static/instantsearch.css
rename to examples/with-algolia-react-instantsearch/styles/global.css
index c13552b4e976b8b..51b5f2e9936c483 100644
--- a/examples/with-algolia-react-instantsearch/public/static/instantsearch.css
+++ b/examples/with-algolia-react-instantsearch/styles/global.css
@@ -1,5 +1,5 @@
-.ais-InstantSearch__root {
- align-items: center;
+html {
+ font-family: sans-serif;
}
header {
@@ -8,11 +8,12 @@ header {
align-items: center;
}
-content {
+main {
display: flex;
+ margin: 25px 0;
}
-menu {
+.menu {
flex: 2;
}
@@ -20,8 +21,12 @@ footer {
text-align: center;
}
-results {
- flex: 10;
+.ais-Pagination {
+ margin-bottom: 25px;
+}
+
+.results {
+ flex: 9;
}
.hit {
diff --git a/examples/with-algolia-react-instantsearch/tsconfig.json b/examples/with-algolia-react-instantsearch/tsconfig.json
new file mode 100644
index 000000000000000..99710e857874ff8
--- /dev/null
+++ b/examples/with-algolia-react-instantsearch/tsconfig.json
@@ -0,0 +1,20 @@
+{
+ "compilerOptions": {
+ "target": "es5",
+ "lib": ["dom", "dom.iterable", "esnext"],
+ "allowJs": true,
+ "skipLibCheck": true,
+ "strict": true,
+ "forceConsistentCasingInFileNames": true,
+ "noEmit": true,
+ "esModuleInterop": true,
+ "module": "esnext",
+ "moduleResolution": "node",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "jsx": "preserve",
+ "incremental": true
+ },
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
+ "exclude": ["node_modules"]
+}
diff --git a/examples/with-algolia-react-instantsearch/utils/index.ts b/examples/with-algolia-react-instantsearch/utils/index.ts
new file mode 100644
index 000000000000000..99c5ce1b4f6f5f0
--- /dev/null
+++ b/examples/with-algolia-react-instantsearch/utils/index.ts
@@ -0,0 +1,10 @@
+import type { SearchState } from 'react-instantsearch-core'
+import qs from 'qs'
+
+export const createURL = (state: SearchState) => `?${qs.stringify(state)}`
+
+export const pathToSearchState = (path: string) =>
+ path.includes('?') ? qs.parse(path.substring(path.indexOf('?') + 1)) : {}
+
+export const searchStateToURL = (searchState: SearchState) =>
+ searchState ? `${window.location.pathname}?${qs.stringify(searchState)}` : ''