Tutorial: Implementing a Google Maps Autocomplete Component with Chakra UI and React Hook Form

Shehzad Ahmed
3 min readJun 13, 2024

--

In this tutorial, we will create a Google Maps Autocomplete component using Chakra UI and React Hook Form. This component will enable users to search for and select locations, enhancing the user interface with autocomplete suggestions.

Implementing a Google Maps Autocomplete Component with Chakra UI and React Hook Form

Prerequisites

Before you start, ensure you have the following installed:

  • Node.js
  • npm or yarn
  • A Google Cloud project with the Places API enabled

Step 1: Setting Up Your Project

First, create a new React project if you haven’t already:

npx create-react-app location-search
cd location-search

Next, install the necessary dependencies:

npm install @chakra-ui/react @chakra-ui/icons @emotion/react @emotion/styled framer-motion react-hook-form lodash react-google-places-autocomplete chakra-react-select

Step 2: Setting Up Chakra UI

Wrap your application with the ChakraProvider to use Chakra UI components:

// src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { ChakraProvider } from '@chakra-ui/react';
import App from './App';

ReactDOM.render(
<ChakraProvider>
<App />
</ChakraProvider>,
document.getElementById('root')
);

Step 3: Creating the LocationSearchSelect Component

Create a new file LocationSearchSelect.js in the src directory:

// src/LocationSearchSelect.js
import React, { useState, useEffect } from 'react';
import { Flex, FormControl, FormErrorMessage, FormLabel, IconButton, InputGroup, chakra, useColorMode } from '@chakra-ui/react';
import { Controller } from 'react-hook-form';
import { Select } from 'chakra-react-select';
import { geocodeByAddress, getLatLng } from 'react-google-places-autocomplete';
import { debounce } from 'lodash';

const LocationSearchSelect = ({ label, placeholder, errors, id, control, required = false, rules, containerStyles, onRefresh, inputProps, isLoading, additionalObj, size, leftAddon, hideLabel, labelProps }) => {
const { colorMode } = useColorMode();
if (required) {
required = `${label} is required`;
}

const [autocompleteService, setAutocompleteService] = useState(null);
const [predictions, setPredictions] = useState([]);

useEffect(() => {
const autocomplete = new window.google.maps.places.AutocompleteService();
setAutocompleteService(autocomplete);
}, []);

const handleSearch = debounce((value) => {
if (autocompleteService) {
autocompleteService.getPlacePredictions(
{ input: value },
(predictions, status) => {
if (status === window.google.maps.places.PlacesServiceStatus.OK) {
setPredictions(predictions);
} else {
setPredictions([]);
}
}
);
}
}, 300);

return (
<Controller
control={control}
name={id}
rules={{
required: required,
...rules
}}
render={({ field: { onChange, onBlur, value, ref, ...rest } }) => {
return (
<Flex align="end" w="full">
<FormControl isInvalid={errors[id]} {...containerStyles}>
{!hideLabel && (
<FormLabel htmlFor={id} fontSize={'13px'} {...labelProps}>
{label}
{required && <chakra.span color="red.500">*</chakra.span>}
</FormLabel>
)}
<InputGroup size={size} w="full">
{leftAddon}
<Select
isLoading={isLoading}
onInputChange={handleSearch}
isClearable={false}
onChange={async (selectedOption) => {
if (selectedOption) {
const results = await geocodeByAddress(selectedOption.label);
const latLng = await getLatLng(results[0]);

let updatedValue = { ...selectedOption, latLng };
if (additionalObj) {
updatedValue = { ...updatedValue, ...additionalObj };
}

onChange(updatedValue);
}
}}
value={value}
ref={ref}
placeholder={placeholder}
options={predictions.map((prediction) => ({
value: prediction.place_id,
label: prediction.description,
}))}
menuPosition="fixed"
classNamePrefix="location-select"
id={id}
{...rest}
{...inputProps}
/>
</InputGroup>
<FormErrorMessage>{errors[id] && errors[id].message}</FormErrorMessage>
</FormControl>
{onRefresh && (
<IconButton onClick={onRefresh} ml="1em" aria-label="Refresh" icon={<Icon boxSize={7} as={APP_ICONS.REFRESH} />} />
)}
</Flex>
);
}}
/>
);
};

export default LocationSearchSelect;

Step 4: Adding the Google Maps Script

Add the Google Maps script to your public/index.html file:

<!DOCTYPE html>
<html lang="en">
<head>
<!-- other head elements -->
<script src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&libraries=places"></script>
</head>
<body>
<div id="root"></div>
</body>
</html>

Replace YOUR_API_KEY with your actual Google Maps API key.

Step 5: Using the Component in Your Form

Now, you can use the LocationSearchSelect component within a form managed by React Hook Form. Here’s an example:

// src/App.js
import React from 'react';
import { useForm, Controller } from 'react-hook-form';
import { Box, Button } from '@chakra-ui/react';
import LocationSearchSelect from './LocationSearchSelect';

const App = () => {
const { handleSubmit, control, formState: { errors } } = useForm();

const onSubmit = (data) => {
console.log(data);
};

return (
<Box p={5}>
<form onSubmit={handleSubmit(onSubmit)}>
<LocationSearchSelect
label="Location"
placeholder="Search for a location"
errors={errors}
id="location"
control={control}
required
/>
<Button mt={4} colorScheme="teal" type="submit">Submit</Button>
</form>
</Box>
);
};

export default App;

Conclusion

You've successfully implemented a Google Maps Autocomplete component using Chakra UI and React Hook Form. This setup provides a robust and user-friendly interface for selecting locations, leveraging the power of Google Places API and the flexibility of React Hook Form.

Find me on your favorite platform

  • Github — Follow me on GitHub for further useful code snippets and open source repos.
  • LinkedIn Profile — Connect with me on LinkedIn for further discussions and updates.
  • Twitter (X) — Connect with me on Twitter (X) for useless tech tweets.

--

--

No responses yet