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)}` : ''