Use Case:
- One can enter source and destination address; For the address field, google places autocomplete is implemented.
- One can also add multiple way-points/stops between source and destination address.
- On change in any address field, The Map and the Directions service is initialized to plot the driving directions and route on map and display the total distance and total duration.
Final Demo!
Step1 — Enable Directions API for Google Maps API key
If you are new to Google Maps API key, I recommend you to have a look at it here.
Step2 — Start Basic CRA and Install packages
npx create-react-app axon
cd axon
npm install @mui/material @emotion/react @emotion/styled
This will create a basic CRA. We also installed MUI 5 for creating UI components.
npm install @react-google-maps/api use-places-autocomplete formik moment
This will install react-google-maps/api
and use-places-autocomplete
packages required.
Step3 — Include Maps JavaScript Library
Include the google maps client-side library in public/index.html
<script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?key [YOUR_API_KEY]&libraries=places"></script>
Step4 — Setup App.js
Initialize Formik with empty initial values of address fields. Formik is the most popular open source form library for React.
import React from "react"; | |
import { ThemeProvider } from "@mui/material/styles"; | |
import * as Mui from "@mui/material"; | |
import { Formik, Form } from "formik"; | |
import theme from "src/configs/theme"; | |
import Location from "src/components/location"; | |
import Map from "src/components/map"; | |
function App() { | |
return ( | |
<ThemeProvider theme={theme}> | |
<div className="main-wrapper"> | |
<Mui.Box className="section-default"> | |
<Mui.Container maxWidth="md" className="pos-relative overflow-visible"> | |
<Mui.Typography className="text-color-white font-weight-normal mb-2" component="h5" variant="h5"> | |
Enter start/end location with multiple waypoints! | |
</Mui.Typography> | |
<Formik | |
enableReinitialize={true} | |
initialValues={{ | |
pickupLocation: "", | |
waypoint: [], | |
dropOffLocation: "", | |
totalDistance: 0, | |
totalTime: "00:00:00", | |
}} | |
onSubmit={() => {}} | |
> | |
{({ values, setFieldValue }) => ( | |
<Form className="default-form pos-relative"> | |
<Mui.Grid container spacing={2} wrap="wrap"> | |
<Mui.Grid item xs={12} md={6}> | |
<Location values={values} setFieldValue={setFieldValue} /> | |
</Mui.Grid> | |
<Mui.Grid item xs={12} md={6}> | |
<Map values={values} setFieldValue={setFieldValue} /> | |
</Mui.Grid> | |
</Mui.Grid> | |
</Form> | |
)} | |
</Formik> | |
</Mui.Container> | |
</Mui.Box> | |
</div> | |
</ThemeProvider> | |
); | |
} | |
export default App; |
Step5 — Create source and destination address field with multiple way-points
import React from "react"; | |
import * as Mui from "@mui/material"; | |
import { Field, FieldArray } from "formik"; | |
import { IconAdd, IconTrash } from "src/components/svg"; | |
import GoogleAutocomplete from "src/components/autocomplete"; | |
const Location = (props) => { | |
return ( | |
<Mui.Card className="default-card-dark-card pos-relative"> | |
<Mui.Grid container spacing={3}> | |
<Mui.Grid item xs={12} md={12} className="form-group-dark"> | |
<Field name="pickupLocation"> | |
{({ field, meta }) => ( | |
<GoogleAutocomplete | |
label={"Start Location"} | |
variant={"outlined"} | |
placeholder="Enter a Location" | |
defaultValue={props.values?.pickupLocation || ""} | |
touched={meta.touched} | |
error={meta.error} | |
callback={(description) => { | |
props.setFieldValue("pickupLocation", description || ""); | |
}} | |
/> | |
)} | |
</Field> | |
</Mui.Grid> | |
<Mui.Grid item xs={12} md={12} className={`w-100`}> | |
<FieldArray | |
name="waypoint" | |
render={(arrayHelpers) => ( | |
<> | |
<Mui.Box display="flex" alignItems="center" className="mb-2"> | |
<Mui.IconButton | |
className="btn-icon-square mr-2" | |
variant="outlined" | |
color="primary" | |
type="button" | |
onClick={() => { | |
if (props.values.waypoint?.length < 5) { | |
arrayHelpers.push({ location: "" }); | |
} | |
}} | |
> | |
<IconAdd /> | |
</Mui.IconButton> | |
<Mui.Typography className="text-color-primary font-weight-normal" component="h5" variant="h5"> | |
Add your waypoint / Add stops | |
</Mui.Typography> | |
</Mui.Box> | |
{props.values.waypoint?.map((item, index) => ( | |
<Mui.Grid container spacing={3} key={index}> | |
<Mui.Grid item xs={12} md={10} className="mt-0 form-group-dark"> | |
<Field name={`waypoint.${index}.location`}> | |
{({ field, meta }) => ( | |
<GoogleAutocomplete | |
variant={"outlined"} | |
placeholder="Enter a Waypoint" | |
callback={(description) => { | |
props.setFieldValue(`waypoint.${index}.location`, description || ""); | |
}} | |
/> | |
)} | |
</Field> | |
</Mui.Grid> | |
<Mui.Grid item xs={12} md={2} className="text-right"> | |
<Mui.IconButton | |
size="small" | |
className={`svg-color-white`} | |
onClick={() => arrayHelpers.remove(index)} | |
> | |
<IconTrash /> | |
</Mui.IconButton> | |
</Mui.Grid> | |
</Mui.Grid> | |
))} | |
</> | |
)} | |
/> | |
</Mui.Grid> | |
<Mui.Grid item xs={12} md={12} className={`form-group-dark`}> | |
<Field name="dropOffLocation"> | |
{({ field, meta }) => ( | |
<GoogleAutocomplete | |
label={"End Location"} | |
variant={"outlined"} | |
placeholder="Enter a Location" | |
defaultValue={props.values?.dropOffLocation || ""} | |
touched={meta.touched} | |
error={meta.error} | |
callback={(description) => { | |
props.setFieldValue("dropOffLocation", description || ""); | |
}} | |
/> | |
)} | |
</Field> | |
</Mui.Grid> | |
</Mui.Grid> | |
</Mui.Card> | |
); | |
}; | |
export default Location; |
The GoogleAutocomplete
field used is a custom google places autocomplete. You can find it here.
Step6 — Configure Request to Direction Service
import React, { useState, useEffect } from "react"; | |
import * as Mui from "@mui/material"; | |
import { GoogleMap, DirectionsRenderer } from "@react-google-maps/api"; | |
import moment from "moment"; | |
import { IconClock, IconRoute } from "src/components/svg"; | |
const Map = (props) => { | |
const [directions, setDirections] = useState(null); | |
const directionsService = new window.google.maps.DirectionsService(); | |
useEffect(() => { | |
if (props.values.pickupLocation) { | |
directionsService.route( | |
{ | |
origin: props.values.pickupLocation, | |
destination: props.values.dropOffLocation ? props.values.dropOffLocation : props.values.pickupLocation, | |
travelMode: window.google.maps.TravelMode.DRIVING, | |
optimizeWaypoints: true, | |
waypoints: props.values.waypoint, | |
unitSystem: window.google.maps.UnitSystem.METRIC, | |
}, | |
(result, status) => { | |
if (status === window.google.maps.DirectionsStatus.OK) { | |
setDirections(result); | |
const route = result.routes[0]; | |
let el = 0; | |
let elt = 0; | |
for (let i = 0; i < route.legs.length; i++) { | |
el += route.legs[i].distance.value; | |
elt += route.legs[i].duration.value; | |
} | |
props.setFieldValue("totalDistance", (el / 1000).toFixed(2)); | |
props.setFieldValue( | |
"totalTime", | |
moment.utc(moment.duration(elt, "seconds").as("milliseconds")).format("HH:mm:ss") | |
); | |
} else { | |
console.error(`Error fetching directions ${result}`); | |
} | |
} | |
); | |
} else { | |
// Reset direction | |
setDirections(null); | |
props.setFieldValue("totalDistance", 0); | |
props.setFieldValue("totalTime", "00:00:00"); | |
} | |
// eslint-disable-next-line react-hooks/exhaustive-deps | |
}, [props.values.pickupLocation, props.values.dropOffLocation, props.values.waypoint]); | |
return ( | |
<Mui.Paper className={`p-0 border-rad-0 shadow-none overflow-hidden`}> | |
<GoogleMap zoom={13} mapContainerStyle={{ height: "60rem" }}> | |
{directions && <DirectionsRenderer directions={directions} />} | |
</GoogleMap> | |
<Mui.Box display="flex" justifyContent="space-between" flexWrap="wrap" className="p-2 background-color-color1"> | |
<Mui.Box display="flex" alignItems="center" className="svg-color-primary svg-size-small-2x"> | |
<IconRoute /> | |
<Mui.Typography component="h3" variant="h3" className="ml-2 font-weight-medium text-color-primary"> | |
<Mui.Typography component="span" variant="body1" className="d-block text-color-white"> | |
Total Distance | |
</Mui.Typography> | |
{props.values.totalDistance} km | |
</Mui.Typography> | |
</Mui.Box> | |
<Mui.Box display="flex" alignItems="center" className="svg-color-primary svg-size-small-2x"> | |
<IconClock /> | |
<Mui.Typography component="h3" variant="h3" className="ml-2 font-weight-medium text-color-primary"> | |
<Mui.Typography component="span" variant="body1" className="d-block text-color-white"> | |
APPROX ETA | |
</Mui.Typography> | |
{moment(props.values.totalTime, "HH:mm:ss").format("H")}h{" "} | |
{moment(props.values.totalTime, "HH:mm:ss").format("m")}m | |
</Mui.Typography> | |
</Mui.Box> | |
</Mui.Box> | |
</Mui.Paper> | |
); | |
}; | |
export default Map; |
On line 9, an instance of the DirectionsService Object is created.
On line 13, the route() called takes directions request JavaScript object (with required query parameters origin
, destination
and travelMode
) as an argument. Also waypoints
parameter is included to consider all the stops between origin and destination.
The second argument of route() method which is a response callback function returns directionsResult
and directionsStatus
.
And that concludes it!
Source Code!
The full source code is available here — https://github.com/anlisha-maharjan/react-google-directions-service
Happy Learning! Feel free to give this article a clap and follow to stay up to date with more articles!
The post Google Maps API Directions Service in React – Plot efficient route on map with total distance and duration. first appeared on Anlisha Maharjan.
Top comments (0)