Let's build a Google Maps clone with React, Leaflet, and OneSDK
Dexter
Posted on May 15, 2023
We will build a project similar to Google Maps. The project will cover some basic features of Google Maps, like pinpointing specific locations on the map or planning of routes between locations. Moreover, users can use geocoding to lookup the location of a postal address.
We will use React and React Leaflet on the frontend, and Node.js, Express.js, and Superface OneSDK on the backend.
Let’s start by creating an empty React project (using create-react-app):
Open your terminal and navigate to the directory where you want to create your project folder.
Run the following command to create a new React project (Replace <project-name> with the desired name for your project):
npx create-react-app <project-name>
Navigate into the project folder using the following command:
cd <project-name>
Open the project in your code editor.
In the src folder, locate the App.js file. This will be the main file where we will make changes for the project. Remove all code in this file.
Optionally remove all unnecessary files, such as test files, default logos etc.
Run the project:
npm start
Adding the map component
To add a map component, you have many options, like Google Maps or Mapbox, but these are not free. We will use Leaflet, an open-source JavaScript library. With Leaflet, you can easily create interactive maps and add markers, pop-ups, and other types of data visualizations. Leaflet support various providers for map assets, but we will stick with the default, OpenStreetMap.
First install leaflet, react-leaflet and leaflet-defaulticon-compatibility. The last package fixes compatibility with Webpack bundler (used by create-react-app) to correctly load images from Leaflet's package.
/* in src/App.css */.leaflet-container{width:100vw;height:100vh;}
And finally, add the following code to App.js file and check if it works:
![Screenshot of website with map and zoom controls.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3v6g5o3xdggkdkvtkhzr.png)
Adding markers on a map
To pinpoint locations on the map, we can use the Marker and Popup components from React Leaflet. The Marker component allows you to add a marker (pin) to a specific location on the map, while the Popup component displays additional information when the marker is clicked or tapped.
Add the following code in App.js:
![Screenshot of website with a map and a marker in the middle with a pop-up with text “Hello World”.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/iw17ylla4hrp2wq9uekn.png)
Back-end: Pinpointing location entered by the user
Now that we understand how to add markers, we can start by pinpointing an address entered by the user. This will involve using a geocoding API to convert the address into a set of coordinates, which can then be used to place a marker on the map.
We will set up a backend API for the map using Node.js and Express. Once a user enters an address, it is translated to location coordinates using a geocoding API – this part will be handled by Superface. We will use the returned coordinates to place the marker on a map.
Setting up the server project
Within the project folder, create a new folder named server to store the server-side code with empty package.json file.
mkdir server
cd server
npm init -y
Next, install Express.js to handle server-side requests.
# in server/ folder
npm install express
Finally, create an empty server.js file in this folder.
Implementing geocoding with OneSDK
I've decided to use Superface to handle API integration because it makes the process incredibly simple. With Superface, I don't have to deal with the hassle of API documentation and I can use multiple providers with the same interface. Additionally, the Superface catalog offers many ready-made API use cases, making it a valuable tool to have in your toolkit.
Then implement a use case. We are going to use Geocoding use case with Nominatim provider. But you can, of course, use a different provider. Copy the example code into your server.js file and make a few changes, so we can send the information we will receive from the user:
// server/server.jsconst{SuperfaceClient}=require('@superfaceai/one-sdk');constsdk=newSuperfaceClient();asyncfunctionrun(loc){// Load the profileconstprofile=awaitsdk.getProfile('address/geocoding@3.1.2');// Use the profileconstresult=awaitprofile.getUseCase('Geocode').perform({query:loc,},{provider:'nominatim',});// Handle the resulttry{constdata=result.unwrap();console.log(data);}catch (error){console.error(error);}}run('Taj Mahal');
Run this function and the coordinates will be returned:
The initial step is to create an input field for the user to enter a location. Upon submission, we will send the location data via a fetch request and use it to determine the coordinates of that location.
Additionally, I am going to use Font Awesome to add icons to our project. This will make it visually appealing and add to its overall design:
cd .. # Go back from server to the main directory with React project
npm install @fortawesome/fontawesome-svg-core @fortawesome/free-solid-svg-icons @fortawesome/free-regular-svg-icons @fortawesome/react-fontawesome@latest
Now we can use the icons in our project. Paste the following code into App.js file:
![Screenshot of map with a search box in the bottom right corner](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lc022x3grzu9vs4ocsof.png)
To preserve the entered location and display it on the map as a pinpoint, we need to retrieve its coordinates using the fetch function and store them using the useState hook.
Paste the following code into App.js file:
// src/App.jsimport'./App.css';import{useState}from'react';import{FontAwesomeIcon}from'@fortawesome/react-fontawesome';import{faLocationDot}from'@fortawesome/free-solid-svg-icons';import'leaflet/dist/leaflet.css';import'leaflet-defaulticon-compatibility/dist/leaflet-defaulticon-compatibility.webpack.css';import{MapContainer,TileLayer,Marker,Popup}from'react-leaflet';import'leaflet-defaulticon-compatibility';functionApp(){const[locationMarkers,setLocationMarkers]=useState([]);asyncfunctionhandleMarkerSubmit(event){event.preventDefault();constformData=newFormData(event.target);constinputLocation=formData.get('location');constres=awaitfetch('/api/geocode?'+newURLSearchParams({location:inputLocation}).toString());if (!res.ok){consterr=awaitres.text();alert(`Something went wrong.\n${err}`);}else{constdata=awaitres.json();letnewLocation={address:data.location,lat:data.coordinates.latitude,long:data.coordinates.longitude,};setLocationMarkers((locations)=>[...locations,newLocation]);}}return (<divclassName="App"><formclassName="inputBlock"onSubmit={handleMarkerSubmit}><inputtype="text"id="location"name="location"requiredplaceholder="Enter location"/><buttontype="submit"className="addloc"><FontAwesomeIconicon={faLocationDot}style={{color:'#1EE2C7'}}/></button></form><MapContainercenter={[51.505,-0.09]}id="mapId"zoom={13}>{locationMarkers.map((loc,key)=>{return (<Markerkey={key}position={[loc.lat,loc.long]}><Popup>{loc.address}</Popup></Marker>);})}<TileLayerurl="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"/></MapContainer></div>);}exportdefaultApp;
And the following code into server.js :
// server/server.jsconstexpress=require('express');constapp=express();const{SuperfaceClient}=require('@superfaceai/one-sdk');constsdk=newSuperfaceClient();constPORT=5000;app.use(express.json());asyncfunctiongeocodeLocation(loc){// Load the profileconstprofile=awaitsdk.getProfile('address/geocoding@3.1.2');// Use the profileconstresult=awaitprofile.getUseCase('Geocode').perform({query:loc,},{provider:'nominatim',});// Handle the resultconstdata=result.unwrap();returndata;}app.get('/api/geocode',async (req,res)=>{try{constlocation=req.query.location;constcoordinates=awaitgeocodeLocation(location);res.json({location,coordinates});}catch (error){res.status(500).json(error);}});app.listen(PORT,()=>{console.log(`Server listening on ${PORT}`);});
Now we will need to start the backend server.
cd server/
npm start
To access the backend server from our React application, we can use requests proxying in Create React App. The server runs on port 5000, so we'll add the following line to the top package.json file in our main project:
"proxy":"http://localhost:5000"
You may need to restart create-react-app server. After doing that, you should be able to search for locations and see markers on your app.
Routing between two locations
Setting up Routing Machine
To create routes between two locations with Leaflet, we will use a Routing Plugin. This plugin will enable us to display routes on the map.
Create a RoutingMachine.js file in src folder and copy and paste the below code. This will allow us to create route between the two different locations we pass to waypoints
Then, we will import this RoutingMachine.js component into our App.js file and provide it with the coordinates of two different locations as props.
Copy and paste the following code in App.js file:
![Screenshot of map with two markers and a red route between them, with navigation instructions in top right corner.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1bijg5ifszek60ugr78o.png)
Routing inputs
We will add input fields for the user to enter two distinct locations, the starting point and the final destination. Then we will ask for coordinates from the server using fetch and pass them as properties to the RoutingMachine.js component. We will also create another route in the server.js file to handle requests for calculating the route between the two locations.
Copy and paste the code in App.js:
// src/App.jsimport'./App.css';import{useState,useEffect}from'react';import{FontAwesomeIcon}from'@fortawesome/react-fontawesome';import{faLocationDot,faRoute}from'@fortawesome/free-solid-svg-icons';import'leaflet/dist/leaflet.css';import'leaflet-defaulticon-compatibility/dist/leaflet-defaulticon-compatibility.webpack.css';import{MapContainer,TileLayer,Marker,Popup}from'react-leaflet';import'leaflet-defaulticon-compatibility';importRoutingMachinefrom'./RoutingMachine';functionApp(){const[locationMarkers,setLocationMarkers]=useState([]);const[waypoints,setWaypoints]=useState();const[showRoutingForm,setFormView]=useState(false);useEffect(()=>{},[waypoints]);asyncfunctionhandleMarkerSubmit(event){event.preventDefault();constformData=newFormData(event.target);constinputLocation=formData.get('location');constres=awaitfetch('/api/geocode?'+newURLSearchParams({location:inputLocation}).toString());if (!res.ok){consterr=awaitres.text();alert(`Something went wrong.\n${err}`);}else{constdata=awaitres.json();letnewLocation={address:data.location,lat:data.coordinates.latitude,long:data.coordinates.longitude,};setLocationMarkers((locations)=>[...locations,newLocation]);}}asyncfunctionhandleRouteSubmit(event){event.preventDefault();// Reset previous waypointsif (waypoints){setWaypoints();}// Hide the formsetFormView(false);constformData=newFormData(event.target);constlocations=formData.getAll('location');constres=awaitfetch('/api/route',{method:'POST',headers:{Accept:'application/json','Content-Type':'application/json;charset=UTF-8',},body:JSON.stringify({locations}),});if (!res.ok){consterr=awaitres.text();alert(`Something went wrong.\n${err}`);}else{constdata=awaitres.json();setWaypoints(data.waypoints);}}return (<divclassName="App"><formclassName="inputBlock"onSubmit={handleMarkerSubmit}><inputtype="text"id="location"name="location"requiredplaceholder="Enter location"/><buttontype="submit"className="addloc"><FontAwesomeIconicon={faLocationDot}style={{color:'#1EE2C7'}}/></button></form><divclassName="routeBlock"><divclassName="addRoutes">{showRoutingForm&&(<formonSubmit={handleRouteSubmit}><divclassName="posOne"><inputtype="text"name="location"requiredplaceholder="Staring Point"/></div><divclassName="posTwo"><inputtype="text"name="location"requiredplaceholder="End Point"/></div><buttonclassName="addloc">Find Path</button></form>)}<FontAwesomeIconicon={faRoute}style={{color:'#1EE2C7'}}onClick={()=>{setFormView((showRoutingForm)=>!showRoutingForm);}}/></div></div><MapContainercenter={[31.505,70.09]}id="mapId"zoom={4}>{locationMarkers.map((loc,key)=>{return (<Markerkey={key}position={[loc.lat,loc.long]}><Popup>{loc.address}</Popup></Marker>);})}<TileLayerurl="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"/>{waypoints?<RoutingMachinewaypoints={waypoints}/>:''}</MapContainer></div>);}exportdefaultApp;
Add the below CSS code in App.css:
/* add to src/App.css *//* Routing form */.routeBlock{position:absolute;left:0.5vw;bottom:2vh;z-index:500;padding:5px;font-size:2rem;border:2pxsolidrgb(41,38,38);background-color:#282c34;}.routeBlockinput{font-size:1rem;}
In this tutorial, we have learned how to create a Google Maps-like application using Leaflet and React. We've utilized Geolocation API to identify location coordinates and place markers, as well as creating a route between two different locations. You can find the final project on GitHub.
There are many more features that can be added to enhance this map project. For example real-time tracking of the user's location, integrating voice assistance for routing, using IP geolocation API, customizing marker icons, and much more.
If you have suggestions on what features to add, or if you'd like to show how you've used this tutorial, leave a comment or come tell us on our Discord. Don't be shy!