user

Mohamed Atef

3 Jul 2021

Auto complete Google addresses API (Reactjs, Material UI, Formik)

React js

Google addresses API with React js 

What is Google Maps JavaScript API?

Google Maps or addresses API is a service from Google Could PlatformWhich allow you to get the key and start searching or fetching data and details about places or addresses using google, and I am going to show you how to use this service with React js, Formik, Material UI

Setup Google addresses API with React js

In the beginning, I searched and tried a lot of Packages but for sorry around 90% of the packages are not supporting Material UI and it's not gonna be simple to use it with Formik so I created a custom way to figure it out, it's really simple, let's start setting up the google places API all you need to do is just getting your project key, From Google Maps Click on enable  

Then from Credentials > Create Credentials and choose API, 

When you finish this step google will ask you to copy your API key copy it and do the next step 

Start working with React js

You need to load the library of google places into your website, we gonna do this from the App folder > public > index.html then add this script at the end of the head tag

<script type="text/javascript" src="//maps.googleapis.com/maps/api/js?key=YOUR_KEY_HERE&language=en&libraries=places"></script>

Now you need to make sure that you installed Formik & Material UI & Material Lab 

Install them in case you don't have them

npm install @material-ui/core

Install Formik

 npm install formik --save

Material UI Lab (to import the Autocomplete input)  

npm install @material-ui/lab

Now you need to set up two components include Google functions to make it easy to use them in the parent component 

First file: GoogleAutoCompelete.js

const GoogleAutoCompelete = async text =>
 new Promise((resolve, reject) => {
 if (!text) {
 return reject("Need valid text input")
 }
 
 // for use in things like GatsbyJS where the html is generated first
 if (typeof window === "undefined") {
 return reject("Need valid window object")
 }
 
 try {
 new window.google.maps.places.AutocompleteService().getQueryPredictions(
 { input: text, componentRestrictions: { country: "us" }, fields: ["address_components", "geometry"], },
 resolve
 )
 } catch (e) {
 reject(e)
 }
 })
export default GoogleAutoCompelete;

Second file: GetPostalCode.js

const GetPostalCode = async placeId =>
 new Promise((resolve, reject) => {
 if (!placeId) reject("placeId not provided")

 try {
 new window.google.maps.places.PlacesService(
 document.createElement("div")
 ).getDetails(
 {
 placeId,
 fields: ["address_components"],
 },
 details => {
 return resolve(details)
 }
 )
 } catch (e) {
 reject(e)
 }
 })
export default GetPostalCode;

Then you have to include them in the main component 

import GoogleAutoCompelete from './Google/GoogleAutoCompelete';
import GetPostalCode from './Google/GetPostalCode';

Now create the form with the functions to use the components 

 <Formik
 enableReinitialize={true}
 initialValues={{
 companyAddress: '',
 zipCode: '',
 city: '',
 stateOfAddress: '',
 submit: null
 }}
 validationSchema={Yup.object().shape({
 companyAddress: Yup.string().max(255).required('Company address is required'),
 zipCode: Yup.string().max(255),
 city: Yup.string().max(255),
 stateOfAddress: Yup.string().max(255),
 })}
 onSubmit={async (values, {
 setErrors,
 setStatus,
 setSubmitting,
 }) => {
 try {
 console.log(values);
 } catch (err) {
 console.error(err);
 setStatus({ success: false });
 setErrors({ submit: err.message });
 setSubmitting(false);
 }
 }}
 >
 {({
 errors,
 handleBlur,
 handleChange,
 handleSubmit,
 isSubmitting,
 touched,
 values,
 setFieldValue
 }) => (
 <form
 onSubmit={handleSubmit}
 {...rest}
 >
 <Grid container> 
 <Grid item md={6} xs={12}> 
 <Box mt={3} px={6}>
 <Grid container spacing={3}>
 <Grid item xs={12}>
 <Autocomplete
 options={addresses.map((option) => option.description)}
 // closeIcon= { () => { return; } }
 onInputChange={(event, value) => { changeAddress(value, setFieldValue); }}
 autoComplete={false}
 renderInput={(params) => (
 <TextField {...params} label="Company Address" name="companyAddress" value={values.companyAddress} onChange={(value) => { handleChangeAddress(value); }} variant="outlined" />
 )} 
 />
 </Grid>
 <Grid item xs={12}>
 <TextField
 error={Boolean(touched.zipCode && errors.zipCode)}
 fullWidth
 helperText={touched.zipCode && errors.zipCode}
 label="Zip Code"
 name="zipCode"
 onBlur={handleBlur}
 onChange={handleChange}
 value={values.zipCode}
 variant="outlined"
 />
 </Grid>
 <Grid item xs={12}>
 <TextField
 error={Boolean(touched.city && errors.city)}
 fullWidth
 helperText={touched.city && errors.city}
 label="City"
 name="city"
 onBlur={handleBlur}
 onChange={handleChange}
 value={values.city}
 variant="outlined"
 />
 </Grid>
 <Grid item xs={12}>
 <TextField
 error={Boolean(touched.stateOfAddress && errors.stateOfAddress)}
 fullWidth
 helperText={touched.stateOfAddress && errors.stateOfAddress}
 label="State (Administrative Area)"
 name="stateOfAddress"
 onBlur={handleBlur}
 onChange={handleChange}
 value={values.stateOfAddress}
 variant="outlined"
 />
 </Grid>
 </Grid>
 {Boolean(touched.tags && errors.tags) && (
 <Box mt={2}>
 <FormHelperText error>
 {errors.tags}
 </FormHelperText>
 </Box>
 )}
 {Boolean(touched.startDate && errors.startDate) && (
 <Box mt={2}>
 <FormHelperText error>
 {errors.startDate}
 </FormHelperText>
 </Box>
 )}
 {Boolean(touched.endDate && errors.endDate) && (
 <Box mt={2}>
 <FormHelperText error>
 {errors.endDate}
 </FormHelperText>
 </Box>
 )}
 </Box>
 <Box
 mt={12}
 display="flex"
 >
 <Box flexGrow={1} />
 <Button
 color="secondary"
 disabled type="submit"
 variant="contained"
 size="large"
 >
 Submit
 </Button>
 </Box>
 </Grid>
 </Grid>
 </form>
 )}
 </Formik>

All we need to use the Google components 

 const [addresses, setAddresses] = useState([]);
 
 const handleChangeAddress = async (searchValue) => {
 const results = await GoogleAutoCompelete(searchValue.target.value);
 if (results) {
 setAddresses(results);
 }
 }

 const changeAddress = async (value, setFieldValue) => {
 let result = null;
 for(let x = 0; x < addresses.length; x++){
 if(value === addresses[x].description){
 result = await GetPostalCode(addresses[x].place_id);
 // Get Zip code
 }
 }
 if(!result){ return ; }
 setFieldValue('companyAddress', value);
 let postcode = null;
 for(let i = 0; i < result.address_components.length; i++){
 if(result.address_components[i].types[0] === 'postal_code'){
 postcode = result.address_components[i].long_name;
 }
 }
 setFieldValue('zipCode', postcode);
 // Get city
 let city = null;
 for(let i = 0; i < result.address_components.length; i++){
 if(result.address_components[i].types[0] === 'locality'){
 city = result.address_components[i].long_name;
 }
 }
 if(!city){
 for(let i = 0; i < result.address_components.length; i++){
 if(result.address_components[i].types[0] === 'administrative_area_level_2'){
 city = result.address_components[i].long_name;
 }
 }
 }
 setFieldValue('city', city);

 // Get State
 let state = null;
 for(let i = 0; i < result.address_components.length; i++){
 if(result.address_components[i].types[0] === 'administrative_area_level_1'){
 state = result.address_components[i].long_name;
 }
 }
 if(!state){
 for(let i = 0; i < result.address_components.length; i++){
 if(result.address_components[i].types[0] === 'administrative_area_level_2'){
 state = result.address_components[i].long_name;
 }
 }
 }
 setFieldValue('stateOfAddress', state);
 }

That's all and here is the content of the main component (AutoFillForm.js)

import React, { useState, useEffect } from 'react';
import * as Yup from 'yup';
import { Formik } from 'formik';
import {
 Box,
 Button,
 FormHelperText,
 Grid,
 TextField,
} from '@material-ui/core';
import Autocomplete from '@material-ui/lab/Autocomplete';
import GoogleAutoCompelete from './Google/GoogleAutoCompelete';
import GetPostalCode from './Google/GetPostalCode';


const AutoFillForm = ({
 className,
 setData,
 data,
 projectId,
 ...rest
}) => {
 const [addresses, setAddresses] = useState([]);
 
 const handleChangeAddress = async (searchValue) => {
 if(!searchValue.target.value){
 return null;
 }
 const results = await GoogleAutoCompelete(searchValue.target.value);
 if (results) {
 setAddresses(results);
 }
 }

 const changeAddress = async (value, setFieldValue) => {
 let result = null;
 for(let x = 0; x < addresses.length; x++){
 if(value === addresses[x].description){
 result = await GetPostalCode(addresses[x].place_id);
 // Get Zip code
 }
 }
 if(!result){ return ; }
 setFieldValue('companyAddress', value);
 let postcode = null;
 for(let i = 0; i < result.address_components.length; i++){
 if(result.address_components[i].types[0] === 'postal_code'){
 postcode = result.address_components[i].long_name;
 }
 }
 setFieldValue('zipCode', postcode);
 // Get city
 let city = null;
 for(let i = 0; i < result.address_components.length; i++){
 if(result.address_components[i].types[0] === 'locality'){
 city = result.address_components[i].long_name;
 }
 }
 if(!city){
 for(let i = 0; i < result.address_components.length; i++){
 if(result.address_components[i].types[0] === 'administrative_area_level_2'){
 city = result.address_components[i].long_name;
 }
 }
 }
 setFieldValue('city', city);

 // Get State
 let state = null;
 for(let i = 0; i < result.address_components.length; i++){
 if(result.address_components[i].types[0] === 'administrative_area_level_1'){
 state = result.address_components[i].long_name;
 }
 }
 if(!state){
 for(let i = 0; i < result.address_components.length; i++){
 if(result.address_components[i].types[0] === 'administrative_area_level_2'){
 state = result.address_components[i].long_name;
 }
 }
 }
 setFieldValue('stateOfAddress', state);
 }

	//We can use this function to disable the browser auto complete from the fields because it looks really annoying
 useEffect(() => {
 window.document.querySelector('input[name="companyAddress"]').setAttribute('autocomplete', 'disable');
 window.document.querySelector('input[name="companyAddress"]').setAttribute('aria-autocomplete', 'off');
 window.document.querySelector('input[name="zipCode"]').setAttribute('autocomplete', 'disable');
 window.document.querySelector('input[name="zipCode"]').setAttribute('aria-autocomplete', 'off');
 window.document.querySelector('input[name="city"]').setAttribute('autocomplete', 'disable');
 window.document.querySelector('input[name="city"]').setAttribute('aria-autocomplete', 'off');
 window.document.querySelector('input[name="stateOfAddress"]').setAttribute('autocomplete', 'disable');
 window.document.querySelector('input[name="stateOfAddress"]').setAttribute('aria-autocomplete', 'off');
 }, []);
 return (
 <Formik
 enableReinitialize={true}
 initialValues={{
 companyAddress: '',
 zipCode: '',
 city: '',
 stateOfAddress: '',
 submit: null
 }}
 validationSchema={Yup.object().shape({
 companyAddress: Yup.string().max(255).required('Company address is required'),
 zipCode: Yup.string().max(255),
 city: Yup.string().max(255),
 stateOfAddress: Yup.string().max(255),
 })}
 onSubmit={async (values, {
 setErrors,
 setStatus,
 setSubmitting,
 }) => {
 try {
 console.log(values);
 } catch (err) {
 console.error(err);
 setStatus({ success: false });
 setErrors({ submit: err.message });
 setSubmitting(false);
 }
 }}
 >
 {({
 errors,
 handleBlur,
 handleChange,
 handleSubmit,
 isSubmitting,
 touched,
 values,
 setFieldValue
 }) => (
 <form
 onSubmit={handleSubmit}
 {...rest}
 >
 <Grid container> 
 <Grid item md={6} xs={12}> 
 <Box mt={3} px={6}>
 <Grid container spacing={3}>
 <Grid item xs={12}>
 <Autocomplete
 options={addresses.map((option) => option.description)}
 // closeIcon= { () => { return; } }
 onInputChange={(event, value) => { changeAddress(value, setFieldValue); }}
 autoComplete={false}
 renderInput={(params) => (
 <TextField {...params} label="Company Address" name="companyAddress" value={values.companyAddress} onChange={(value) => { handleChangeAddress(value); }} variant="outlined" />
 )} 
 />
 </Grid>
 <Grid item xs={12}>
 <TextField
 error={Boolean(touched.zipCode && errors.zipCode)}
 fullWidth
 helperText={touched.zipCode && errors.zipCode}
 label="Zip Code"
 name="zipCode"
 onBlur={handleBlur}
 onChange={handleChange}
 value={values.zipCode}
 variant="outlined"
 />
 </Grid>
 <Grid item xs={12}>
 <TextField
 error={Boolean(touched.city && errors.city)}
 fullWidth
 helperText={touched.city && errors.city}
 label="City"
 name="city"
 onBlur={handleBlur}
 onChange={handleChange}
 value={values.city}
 variant="outlined"
 />
 </Grid>
 <Grid item xs={12}>
 <TextField
 error={Boolean(touched.stateOfAddress && errors.stateOfAddress)}
 fullWidth
 helperText={touched.stateOfAddress && errors.stateOfAddress}
 label="State (Administrative Area)"
 name="stateOfAddress"
 onBlur={handleBlur}
 onChange={handleChange}
 value={values.stateOfAddress}
 variant="outlined"
 />
 </Grid>
 </Grid>
 {Boolean(touched.tags && errors.tags) && (
 <Box mt={2}>
 <FormHelperText error>
 {errors.tags}
 </FormHelperText>
 </Box>
 )}
 {Boolean(touched.startDate && errors.startDate) && (
 <Box mt={2}>
 <FormHelperText error>
 {errors.startDate}
 </FormHelperText>
 </Box>
 )}
 {Boolean(touched.endDate && errors.endDate) && (
 <Box mt={2}>
 <FormHelperText error>
 {errors.endDate}
 </FormHelperText>
 </Box>
 )}
 </Box>
 <Box
 mt={12}
 display="flex"
 >
 <Box flexGrow={1} />
 <Button
 color="secondary"
 disabled type="submit"
 variant="contained"
 size="large"
 >
 Submit
 </Button>
 </Box>
 </Grid>
 </Grid>
 </form>
 )}
 </Formik>
 );
};

export default AutoFillForm;

If you have any questions let me know in the comments below 
Thanks

Comments

Clement Gitonga

19 Mar 2022

I got an error “Expected to return a value at the end of arrow function”  on this line in GoogleAutoComplete

 new Promise((resolve, reject) => {

Clement Gitonga

23 Mar 2022

I figured out the solution to the error above. Thank you for this blog post I was able to solve a major problem. in addition I used the same concept to get Distance between two points.

Arun Chapagain

1 Aug 2022

Can you provide git hub link for this?

 

© 2024 Copyrights reserved for web-brackets.com