├── .prettierrc.yml ├── src ├── index.js ├── index.d.ts ├── useStableValue.js ├── usePlaceKit.js └── PlaceKit.jsx ├── .gitignore ├── .eslintrc.yml ├── .github └── workflows │ └── ci.yml ├── LICENSE ├── rollup.config.js ├── package.json └── README.md /.prettierrc.yml: -------------------------------------------------------------------------------- 1 | singleQuote: true 2 | printWidth: 100 3 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | export { default as PlaceKit } from './PlaceKit'; 2 | export { usePlaceKit } from './usePlaceKit'; 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # misc 4 | .DS_Store 5 | /demo 6 | /NOTES.md 7 | /*.sh 8 | 9 | # dependencies 10 | node_modules 11 | 12 | # build 13 | /dist 14 | 15 | # log files 16 | *.log* -------------------------------------------------------------------------------- /.eslintrc.yml: -------------------------------------------------------------------------------- 1 | root: true 2 | env: 3 | browser: true, 4 | es2022: true 5 | extends: 6 | - prettier 7 | plugins: 8 | - react 9 | - prettier 10 | settings: 11 | react: 12 | version: detect 13 | parserOptions: 14 | ecmaVersion: latest 15 | ecmaFeatures: 16 | jsx: true 17 | sourceType: module 18 | rules: 19 | prettier/prettier: error 20 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | 9 | jobs: 10 | run: 11 | name: Lint & Build 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v3 15 | - uses: actions/setup-node@v3 16 | with: 17 | node-version: 18 18 | cache: 'npm' 19 | cache-dependency-path: '**/package-lock.json' 20 | - run: npm ci --ignore-scripts 21 | - run: npm run lint 22 | - run: npm run build -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 PlaceKit 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import path from 'node:path'; 2 | 3 | import { babel } from '@rollup/plugin-babel'; 4 | import { nodeResolve } from '@rollup/plugin-node-resolve'; 5 | import copy from 'rollup-plugin-copy'; 6 | 7 | import pkg from './package.json' assert { type: 'json' }; 8 | const banner = [ 9 | `/*! ${pkg.name} v${pkg.version}`, 10 | '© placekit.io', 11 | `${pkg.license} license`, 12 | `${pkg.homepage} */`, 13 | ].join(' | '); 14 | 15 | export default { 16 | input: 'src/index.js', 17 | output: [ 18 | { 19 | file: pkg.module, 20 | format: 'es', 21 | banner, 22 | }, 23 | { 24 | file: pkg.main, 25 | format: 'cjs', 26 | exports: 'auto', 27 | banner, 28 | }, 29 | ], 30 | external: [/node_modules/], 31 | plugins: [ 32 | nodeResolve({ 33 | preferBuiltins: true, 34 | extensions: ['.js', '.jsx'], 35 | }), 36 | babel({ 37 | presets: ['@babel/preset-react'], 38 | babelHelpers: 'bundled', 39 | }), 40 | copy({ 41 | targets: [ 42 | { 43 | src: 'src/index.d.ts', 44 | dest: path.dirname(pkg.types), 45 | rename: path.basename(pkg.types), 46 | transform: (content) => [banner, content].join("\n"), 47 | }, 48 | ] 49 | }) 50 | ], 51 | }; 52 | -------------------------------------------------------------------------------- /src/index.d.ts: -------------------------------------------------------------------------------- 1 | import type { PKAClient, PKAHandlers, PKAOptions, PKAState } from '@placekit/autocomplete-js'; 2 | 3 | type Handlers = { 4 | onOpen: PKAHandlers['open']; 5 | onClose: PKAHandlers['close']; 6 | onResults: PKAHandlers['results']; 7 | onPick: PKAHandlers['pick']; 8 | onError: PKAHandlers['error']; 9 | onCountryChange: PKAHandlers['countryChange']; 10 | onDirty: PKAHandlers['dirty']; 11 | onEmpty: PKAHandlers['empty']; 12 | onFreeForm: PKAHandlers['freeForm']; 13 | onGeolocation: PKAHandlers['geolocation']; 14 | onCountryMode: PKAHandlers['countryMode']; 15 | onState: PKAHandlers['state']; 16 | }; 17 | 18 | export type PlaceKitProps = { 19 | apiKey: string; 20 | geolocation?: boolean; 21 | className?: string; 22 | options?: Omit; 23 | onClient?: (client?: PKAClient) => void; 24 | } & Partial & 25 | React.HTMLProps; 26 | 27 | export type PlaceKitOptions = Omit & { 28 | handlers?: Partial; 29 | }; 30 | 31 | export type PlaceKitHooks = { 32 | target: React.RefObject; 33 | client: PKAClient; 34 | state: PKAState; 35 | }; 36 | 37 | declare function PlaceKit(props: PlaceKitProps): JSX.Element; 38 | 39 | declare function usePlaceKit(apiKey: string, options?: PlaceKitOptions): PlaceKitHooks; 40 | -------------------------------------------------------------------------------- /src/useStableValue.js: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | 3 | // dequal borrowed from https://github.com/lukeed/dequal/blob/master/src/lite.js 4 | const has = Object.prototype.hasOwnProperty; 5 | 6 | function dequal(foo, bar) { 7 | let ctor; 8 | let len; 9 | if (foo === bar) return true; 10 | 11 | if (foo && bar && (ctor = foo.constructor) === bar.constructor) { 12 | if (ctor === Date) return foo.getTime() === bar.getTime(); 13 | if (ctor === RegExp) return foo.toString() === bar.toString(); 14 | 15 | if (ctor === Array) { 16 | if ((len = foo.length) === bar.length) { 17 | while (len-- && dequal(foo[len], bar[len])); 18 | } 19 | return len === -1; 20 | } 21 | 22 | if (!ctor || typeof foo === 'object') { 23 | len = 0; 24 | // eslint-disable-next-line guard-for-in 25 | for (ctor in foo) { 26 | if (has.call(foo, ctor) && ++len && !has.call(bar, ctor)) return false; 27 | if (!(ctor in bar) || !dequal(foo[ctor], bar[ctor])) return false; 28 | } 29 | return Object.keys(bar).length === len; 30 | } 31 | } 32 | 33 | return foo !== foo && bar !== bar; 34 | } 35 | 36 | // prevent re-renders when options prop is an object literal 37 | export function useStableValue(value) { 38 | const [stableValue, setStableValue] = useState(() => value); 39 | if (!dequal(stableValue, value)) { 40 | setStableValue(value); 41 | } 42 | return stableValue; 43 | } 44 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@placekit/autocomplete-react", 3 | "version": "2.2.2", 4 | "author": "PlaceKit ", 5 | "description": "PlaceKit Autocomplete React library", 6 | "keywords": [ 7 | "addresses", 8 | "autocomplete", 9 | "geocoder", 10 | "geocoding", 11 | "locations", 12 | "react", 13 | "search" 14 | ], 15 | "license": "MIT", 16 | "homepage": "https://github.com/placekit/autocomplete-react#readme", 17 | "repository": { 18 | "type": "git", 19 | "url": "git+https://github.com/placekit/autocomplete-react.git" 20 | }, 21 | "bugs": { 22 | "url": "https://github.com/placekit/autocomplete-react/issues" 23 | }, 24 | "type": "module", 25 | "main": "./dist/placekit-react.cjs.js", 26 | "module": "./dist/placekit-react.esm.mjs", 27 | "types": "./dist/placekit-react.d.ts", 28 | "exports": { 29 | ".": { 30 | "types": "./dist/placekit-react.d.ts", 31 | "require": "./dist/placekit-react.cjs.js", 32 | "import": "./dist/placekit-react.esm.mjs" 33 | } 34 | }, 35 | "files": [ 36 | "dist", 37 | "LICENSE" 38 | ], 39 | "watch": { 40 | "build": "src/*.*" 41 | }, 42 | "scripts": { 43 | "clear": "rimraf ./dist", 44 | "dev": "npm-watch build", 45 | "build": "rollup -c", 46 | "lint": "eslint ./src", 47 | "format": "prettier --write ./src" 48 | }, 49 | "devDependencies": { 50 | "@babel/core": "^7.23.9", 51 | "@babel/preset-react": "^7.23.3", 52 | "@rollup/plugin-babel": "^6.0.4", 53 | "@rollup/plugin-node-resolve": "^15.2.3", 54 | "eslint": "^8.57.0", 55 | "eslint-config-prettier": "^9.1.0", 56 | "eslint-plugin-prettier": "^5.1.3", 57 | "eslint-plugin-react": "^7.33.2", 58 | "npm-watch": "^0.11.0", 59 | "prettier": "^3.2.5", 60 | "rimraf": "^5.0.5", 61 | "rollup": "^4.12.0", 62 | "rollup-plugin-copy": "^3.5.0" 63 | }, 64 | "dependencies": { 65 | "@placekit/autocomplete-js": "^2.2.1", 66 | "@types/react": "^18.2.58", 67 | "prop-types": "^15.8.1" 68 | }, 69 | "peerDependencies": { 70 | "react": "^16 || ^17 || ^18", 71 | "react-dom": "^16 || ^17 || ^18" 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/usePlaceKit.js: -------------------------------------------------------------------------------- 1 | import placekitAutocomplete from '@placekit/autocomplete-js'; 2 | import { useEffect, useRef, useState } from 'react'; 3 | 4 | import { useStableValue } from './useStableValue'; 5 | 6 | export const usePlaceKit = (apiKey, options = {}) => { 7 | // throw error if invalid options 8 | if (typeof options !== 'object' || Array.isArray(options) || options === null) { 9 | throw Error('PlaceKit: `options` parameter is invalid, expected an object.'); 10 | } 11 | 12 | // init states 13 | const stableOptions = useStableValue(options); 14 | const target = useRef(null); 15 | const [client, setClient] = useState(); 16 | const [state, setState] = useState({ 17 | dirty: false, 18 | empty: true, 19 | freeForm: true, 20 | geolocation: false, 21 | countryMode: false, 22 | }); 23 | 24 | // mount PlaceKit Autocomplete JS 25 | useEffect(() => { 26 | if (!target.current) { 27 | return; 28 | } 29 | 30 | const pka = placekitAutocomplete(apiKey, { 31 | target: target.current, 32 | }); 33 | setClient(pka); 34 | 35 | return () => { 36 | pka.destroy(); 37 | setClient(); 38 | }; 39 | }, [apiKey, target.current]); 40 | 41 | // run `pka.configure()` when options update 42 | useEffect(() => { 43 | if (!client) { 44 | return; 45 | } 46 | 47 | const { handlers, ...opts } = stableOptions || {}; 48 | client 49 | .on('open', handlers?.onOpen) 50 | .on('close', handlers?.onClose) 51 | .on('results', handlers?.onResults) 52 | .on('pick', handlers?.onPick) 53 | .on('error', handlers?.onError) 54 | .on('countryChange', handlers?.onCountryChange) 55 | .on('dirty', handlers?.onDirty) 56 | .on('empty', handlers?.onEmpty) 57 | .on('freeForm', handlers?.onFreeForm) 58 | .on('geolocation', handlers?.onGeolocation) 59 | .on('countryMode', handlers?.onCountryMode) 60 | .on('state', ({ ...newState }) => { 61 | // spread to remove `client.state` reference 62 | setState(newState); 63 | if (handlers?.onState) { 64 | handlers.onState(newState); 65 | } 66 | }) 67 | .configure(opts); 68 | 69 | // inject initial state from client (spread to remove `client.state` reference) 70 | setState({ ...client.state }); 71 | }, [client, stableOptions]); 72 | 73 | return { 74 | target, 75 | client, 76 | state, 77 | }; 78 | }; 79 | -------------------------------------------------------------------------------- /src/PlaceKit.jsx: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import React, { forwardRef, useCallback, useEffect } from 'react'; 3 | 4 | import { usePlaceKit } from './usePlaceKit'; 5 | 6 | const PlaceKit = forwardRef( 7 | ( 8 | { 9 | apiKey, 10 | className, 11 | geolocation, 12 | options, 13 | onClient, 14 | onOpen, 15 | onClose, 16 | onResults, 17 | onPick, 18 | onError, 19 | onCountryChange, 20 | onDirty, 21 | onEmpty, 22 | onFreeForm, 23 | onGeolocation, 24 | onCountryMode, 25 | onState, 26 | ...inputProps 27 | }, 28 | ref, 29 | ) => { 30 | const { target, client, state } = usePlaceKit(apiKey, { 31 | ...options, 32 | handlers: { 33 | onOpen, 34 | onClose, 35 | onResults, 36 | onPick, 37 | onError, 38 | onCountryChange, 39 | onDirty, 40 | onEmpty, 41 | onFreeForm, 42 | onGeolocation, 43 | onCountryMode, 44 | onState, 45 | }, 46 | }); 47 | 48 | // update default value (only if untouched) 49 | useEffect(() => { 50 | if (client && !client.state.dirty) { 51 | client.setValue(inputProps.defaultValue); 52 | } 53 | }, [inputProps.defaultValue, client]); 54 | 55 | // forward ref from `target` 56 | useEffect(() => { 57 | if (target.current && ref) { 58 | if (typeof ref === 'function') { 59 | ref(target.current); 60 | } else { 61 | ref.current = target.current; 62 | } 63 | } 64 | }, [target.current]); 65 | 66 | // pass client to `onClient` when it updates 67 | useEffect(() => { 68 | if (onClient?.call) { 69 | onClient(client); 70 | } 71 | }, [client, onClient]); 72 | 73 | // toggle geolocation 74 | const toggleGeolocation = useCallback(() => { 75 | if (client) { 76 | if (client.state.geolocation) { 77 | client.clearGeolocation(); 78 | } else { 79 | client.requestGeolocation(); 80 | } 81 | } 82 | }, [client]); 83 | 84 | return ( 85 |
c).join(' ')}> 86 | {!!geolocation && ( 87 | 100 | )} 101 | 111 | 112 |
113 | ); 114 | }, 115 | ); 116 | 117 | PlaceKit.defaultProps = { 118 | geolocation: true, 119 | options: {}, 120 | placeholder: 'Search places...', 121 | }; 122 | 123 | PlaceKit.propTypes = { 124 | // component options 125 | geolocation: PropTypes.bool, 126 | className: PropTypes.string, 127 | 128 | // PlaceKit Autocomplete JS options 129 | apiKey: PropTypes.string.isRequired, 130 | options: PropTypes.shape({ 131 | panel: PropTypes.shape({ 132 | className: PropTypes.string, 133 | offset: PropTypes.number, 134 | strategy: PropTypes.oneOf(['absolute', 'fixed']), 135 | flip: PropTypes.bool, 136 | }), 137 | format: PropTypes.shape({ 138 | flag: PropTypes.func, 139 | icon: PropTypes.func, 140 | sub: PropTypes.func, 141 | noResults: PropTypes.func, 142 | applySuggestion: PropTypes.string, 143 | cancel: PropTypes.string, 144 | }), 145 | countryAutoFill: PropTypes.bool, 146 | countrySelect: PropTypes.bool, 147 | timeout: PropTypes.number, 148 | maxResults: PropTypes.number, 149 | types: PropTypes.arrayOf(PropTypes.string), 150 | language: PropTypes.string, 151 | countries: PropTypes.arrayOf(PropTypes.string), 152 | coordinates: PropTypes.string, 153 | }), 154 | 155 | // event handlers 156 | onClient: PropTypes.func, 157 | onOpen: PropTypes.func, 158 | onClose: PropTypes.func, 159 | onResults: PropTypes.func, 160 | onPick: PropTypes.func, 161 | onError: PropTypes.func, 162 | onDirty: PropTypes.func, 163 | onEmpty: PropTypes.func, 164 | onFreeForm: PropTypes.func, 165 | onGeolocation: PropTypes.func, 166 | onCountryMode: PropTypes.func, 167 | onState: PropTypes.func, 168 | onCountryChange: PropTypes.func, 169 | 170 | // other HTML input props get forwarded 171 | }; 172 | 173 | export default PlaceKit; 174 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | PlaceKit Autocomplete React Library 3 |

4 | 5 |

6 | All-in-one autocomplete experience for your React web apps 7 |

8 | 9 |
10 | 11 | [![NPM](https://img.shields.io/npm/v/@placekit/autocomplete-react?style=flat-square)](https://www.npmjs.com/package/@placekit/autocomplete-react?activeTab=readme) 12 | [![LICENSE](https://img.shields.io/github/license/placekit/autocomplete-react?style=flat-square)](./LICENSE) 13 | 14 |
15 | 16 |

17 | Quick start • 18 | Component properties • 19 | Custom hook • 20 | Troubleshoot • 21 | License • 22 | Examples 23 |

24 | 25 | --- 26 | 27 | PlaceKit Autocomplete React Library is a React wrapper of [PlaceKit Autocomplete JS](https://github.com/placekit/autocomplete-js). 28 | It features a [component](./src/PlaceKit.jsx) ready to use, and a [custom hook](./src/usePlaceKit.js) for custom implementations. 29 | It also is **TypeScript compatible**. 30 | 31 | ## 🎯 Quick start 32 | 33 | First, install PlaceKit Autocomplete React using [npm](https://docs.npmjs.com/getting-started) package manager: 34 | 35 | ```sh 36 | npm install --save @placekit/autocomplete-react 37 | ``` 38 | 39 | Then import the package and perform your first address search: 40 | 41 | ```jsx 42 | import { PlaceKit } from '@placekit/autocomplete-react'; 43 | import '@placekit/autocomplete-js/dist/placekit-autocomplete.css'; 44 | 45 | const MyComponent = (props) => { 46 | return ( 47 | 48 | ); 49 | }; 50 | 51 | export default MyComponent; 52 | ``` 53 | 54 | Importing default style from `@placekit/autocomplete-js/dist/placekit-autocomplete.css` (`@placekit/autocomplete-js` is set as a dependency of this package and will automatically be installed) will style the suggestions list and the input. 55 | If you have trouble importing CSS from `node_modules`, copy/paste [its content](https://github.com/placekit/autocomplete-js/blob/main/src/placekit-autocomplete.css) into your own CSS. 56 | 57 | 👉 **Check out our [examples](https://github.com/placekit/examples) for different use cases!** 58 | 59 | ## ⚙️ Component properties 60 | 61 | ```jsx 62 | wrapper custom classes 66 | 67 | // PlaceKit Autocomplete JS options 68 | apiKey="" 69 | options={{ 70 | panel: { 71 | className: 'panel-custom-class', 72 | offset: 4, 73 | strategy: 'absolute', 74 | flip: false, 75 | }, 76 | format: { 77 | flag: (countrycode) => {}, 78 | icon: (name, label) => {}, 79 | sub: (item) => {}, 80 | noResults: (query) => {}, 81 | value: (item) => {}, 82 | applySuggestion: 'Apply suggestion', 83 | cancel: 'Cancel', 84 | }, 85 | countryAutoFill: false, 86 | countrySelect: false, 87 | timeout: 5000, 88 | maxResults: 5, 89 | types: ['city'], 90 | language: 'fr', 91 | countries: ['fr'], 92 | coordinates: '48.86,2.29', 93 | }} 94 | 95 | // event handlers (⚠️ use useCallback, see notes) 96 | onClient={(client) => {}} 97 | onOpen={() => {}} 98 | onClose={() => {}} 99 | onResults={(query, results) => {}} 100 | onPick={(value, item, index) => {}} 101 | onError={(error) => {}} 102 | onCountryChange={(item) => {}} 103 | onDirty={(bool) => {}} 104 | onEmpty={(bool) => {}} 105 | onFreeForm={(bool) => {}} 106 | onGeolocation={(bool, position) => {}} 107 | onCountryMode={(bool) => {}} 108 | onState={(state) => {}} 109 | 110 | // other HTML input props get forwarded 111 | id="my-input" 112 | name="address" 113 | placeholder="Search places..." 114 | disabled={true} 115 | defaultValue="France" 116 | // ... 117 | /> 118 | ``` 119 | 120 | Please refer to [PlaceKit Autocomplete JS](https://github.com/placekit/autocomplete-js#pkaoptions) documentation for more details about the options. 121 | 122 | Some additional notes: 123 | - If you want to customize the input style, create your own component using our [custom hook](#-custom-hook). You can reuse our component as a base. 124 | - If you want to customize the suggestions panel style, don't import our stylesheet and create your own following [PlaceKit Autocomplete JS](https://github.com/placekit/autocomplete-js#-customize) documentation. 125 | - Handlers are exposed directly as properties for ease of access. 126 | - It's recommended to memoize handler functions with `useCallback`, see [Avoid re-renders](#avoid-re-renders). 127 | - ⚠️ The `` it is an **uncontrolled component**. See [dynamic default value](#dynamic-default-value). 128 | 129 | ## 🪝 Custom hook 130 | 131 | If our component doesn't suit your needs, you can build your own using the provided custom hook `usePlaceKit()`: 132 | 133 | ```jsx 134 | import { usePlaceKit } from '@placekit/autocomplete-react'; 135 | 136 | const MyComponent = (props) => { 137 | const { target, client, state } = usePlaceKit('', { 138 | // options 139 | }); 140 | 141 | return ( 142 | 143 | ); 144 | }; 145 | ``` 146 | 147 | Please refer to [PlaceKit Autocomplete JS](https://github.com/placekit/autocomplete-js#pkaoptions) documentation for more details about the options. 148 | 149 | Some additional notes: 150 | - `target` is a React `ref` object. 151 | - The handlers can be passed through `options.handlers`, but also be set with `client.on()` (better use a `useState()` in that case) except for `onClient` which is specific to Autocomplete React. 152 | - `state` exposes stateless client properties (`dirty`, `empty`, `freeForm`, `geolocation`, `countryMode`) as stateful ones. 153 | 154 | ⚠️ **NOTE:** you are **not** allowed to hide the PlaceKit logo unless we've delivered a special authorization. To request one, please contact us using [our contact form](https://placekit.io/about#contact). 155 | 156 | ## 🚒 Troubleshoot 157 | 158 | ### Access Autocomplete JS client from parent component 159 | 160 | Use the extra `onClient` handler to access the client from the parent component: 161 | 162 | ```jsx 163 | import { PlaceKit } from '@placekit/autocomplete-react'; 164 | import { useEffect, useState } from 'react'; 165 | 166 | const MyComponent = (props) => { 167 | const [client, setClient] = useState(); 168 | 169 | useEffect( 170 | () => { 171 | if (client) { 172 | // do something 173 | } 174 | }, 175 | [client] 176 | ); 177 | 178 | return ( 179 | 183 | ); 184 | }; 185 | ``` 186 | 187 | `onClient` is an exception for handlers: it's **specific to Autocomplete React** and isn't passed in `options.handlers` to Autocomplete JS, so updating it doesn't trigger `pka.configure()`. 188 | 189 | Please refer to [PlaceKit Autocomplete JS](https://github.com/placekit/autocomplete-js#-reference) documentation for more details about the client methods. 190 | 191 | ### Avoid re-renders 192 | 193 | Because of the way React works, object/array/function literals are always considered fresh values and may cause unnecessary re-renders. 194 | 195 | `` is mostly just a wrapper around [PlaceKit Autocomplete JS](https://github.com/placekit/autocomplete-js): it uses `useEffect` to mount it and update options. We've made it quite resilient to updates, but each time `options` updates, `pka.configure()` is called and makes some computations. 196 | 197 | To avoid unnecessary re-renders, memoize or hoist those literals: 198 | 199 | ```jsx 200 | import { PlaceKit } from '@placekit/autocomplete-react'; 201 | import { useCallback } from 'react'; 202 | 203 | // hoisting option functions (declaring outside of the component) 204 | const formatValue = (item) => item.name; 205 | 206 | const MyComponent = (props) => { 207 | 208 | // memoizing event handlers with useCallback 209 | const handlePick = useCallback( 210 | (value, item) => { 211 | console.log(item); 212 | }, 213 | [] 214 | ); 215 | 216 | return ( 217 | 225 | ); 226 | }; 227 | ``` 228 | 229 | ⚠️ If you need `apiKey` to be set dynamically, use `useMemo` to memoize it, otherwise the whole autocomplete will reset at each component update, flushing the suggestions list. 230 | 231 | ### Dynamic default value 232 | 233 | The `` is an [**uncontrolled component**](https://react.dev/learn/sharing-state-between-components#controlled-and-uncontrolled-components) and should be treated as such. Making it controlled may cause typing glitches because React state in controlled components can conflict with Autocomplete JS internal state management. 234 | 235 | You can dyamically set a `defaultValue` and it'll update the input value as long as it has not been touched by the user (`state.dirty === false`). 236 | 237 | ⚠️ Passing a non-empty value to `defaultValue` will automatically trigger a first search request when the user focuses the input. 238 | 239 | ## ⚖️ License 240 | 241 | PlaceKit Autocomplete React Library is an open-sourced software licensed under the [MIT license](./LICENSE). 242 | --------------------------------------------------------------------------------