From 994089fcc2d17b285cbe4e00839bd6eb9f1b426f Mon Sep 17 00:00:00 2001 From: Max Proske Date: Mon, 14 Nov 2022 04:30:57 -0800 Subject: [PATCH] Improve `with-algolia-react-instantsearch` example and convert to TypeScript (#42617) Converted to TypeScript to match Contribution docs, and updated/simplified the example. - Replaced stylesheets in Head component with imported styles - Removed `style-loader`, `css-loader`, `cross-env`, `prop-types` packages - Removed custom webpack config ## Documentation / Examples - [X] Make sure the linting passes by running `pnpm build && pnpm lint` - [X] The "examples guidelines" are followed from [our contributing doc](https://github.com/vercel/next.js/blob/canary/contributing/examples/adding-examples.md) --- .../components/Search.tsx | 56 ++++++++++++ .../components/app.js | 81 ----------------- .../components/head.js | 48 ---------- .../components/index.js | 3 - .../components/instantsearch.js | 13 --- .../next.config.js | 17 ---- .../package.json | 22 +++-- .../pages/_app.tsx | 7 ++ .../pages/index.js | 60 ------------- .../pages/index.tsx | 84 ++++++++++++++++++ .../public/favicon.ico | Bin 15086 -> 0 bytes .../instantsearch.css => styles/global.css} | 17 ++-- .../tsconfig.json | 20 +++++ .../utils/index.ts | 10 +++ 14 files changed, 202 insertions(+), 236 deletions(-) create mode 100644 examples/with-algolia-react-instantsearch/components/Search.tsx delete mode 100644 examples/with-algolia-react-instantsearch/components/app.js delete mode 100644 examples/with-algolia-react-instantsearch/components/head.js delete mode 100644 examples/with-algolia-react-instantsearch/components/index.js delete mode 100644 examples/with-algolia-react-instantsearch/components/instantsearch.js delete mode 100644 examples/with-algolia-react-instantsearch/next.config.js create mode 100644 examples/with-algolia-react-instantsearch/pages/_app.tsx delete mode 100644 examples/with-algolia-react-instantsearch/pages/index.js create mode 100644 examples/with-algolia-react-instantsearch/pages/index.tsx delete mode 100644 examples/with-algolia-react-instantsearch/public/favicon.ico rename examples/with-algolia-react-instantsearch/{public/static/instantsearch.css => styles/global.css} (74%) create mode 100644 examples/with-algolia-react-instantsearch/tsconfig.json create mode 100644 examples/with-algolia-react-instantsearch/utils/index.ts 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 4965832f2c9b0605eaa189b7c7fb11124d24e48a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15086 zcmeHOOH5Q(7(R0cc?bh2AT>N@1PWL!LLfZKyG5c!MTHoP7_p!sBz0k$?pjS;^lmgJ zU6^i~bWuZYHL)9$wuvEKm~qo~(5=Lvx5&Hv;?X#m}i|`yaGY4gX+&b>tew;gcnRQA1kp zBbm04SRuuE{Hn+&1wk%&g;?wja_Is#1gKoFlI7f`Gt}X*-nsMO30b_J@)EFNhzd1QM zdH&qFb9PVqQOx@clvc#KAu}^GrN`q5oP(8>m4UOcp`k&xwzkTio*p?kI4BPtIwX%B zJN69cGsm=x90<;Wmh-bs>43F}ro$}Of@8)4KHndLiR$nW?*{Rl72JPUqRr3ta6e#A z%DTEbi9N}+xPtd1juj8;(CJt3r9NOgb>KTuK|z7!JB_KsFW3(pBN4oh&M&}Nb$Ee2 z$-arA6a)CdsPj`M#1DS>fqj#KF%0q?w50GN4YbmMZIoF{e1yTR=4ablqXHBB2!`wM z1M1ke9+<);|AI;f=2^F1;G6Wfpql?1d5D4rMr?#f(=hkoH)U`6Gb)#xDLjoKjp)1;Js@2Iy5yk zMXUqj+gyk1i0yLjWS|3sM2-1ECc;MAz<4t0P53%7se$$+5Ex`L5TQO_MMXXi04UDIU+3*7Ez&X|mj9cFYBXqM{M;mw_ zpw>azP*qjMyNSD4hh)XZt$gqf8f?eRSFX8VQ4Y+H3jAtvyTrXr`qHAD6`m;aYmH2zOhJC~_*AuT} zvUxC38|JYN94i(05R)dVKgUQF$}#cxV7xZ4FULqFCNX*Forhgp*yr6;DsIk=ub0Hv zpk2L{9Q&|uI^b<6@i(Y+iSxeO_n**4nRLc`P!3ld5jL=nZRw6;DEJ*1z6Pvg+eW|$lnnjO zjd|8>6l{i~UxI244CGn2kK@cJ|#ecwgSyt&HKA2)z zrOO{op^o*- `?${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)}` : ''