Build a live map application with React
You will need Node 6+ and npx installed on your machine.
Today, almost all of us rely on map applications for directions, distances and local information… Now, we even have the ability to share our live location with friends, but imagine a scenario where a group of three friends are trying to meet at a particular location, each one of the friends needs to send a link to the others for them to know where they are.
In this article, we are going to use Pusher Channels to create a React application that allows you to see the location of your friends in realtime when they are online on the app. By the end of this article, you should have an application that looks like this:
Prerequisites
To follow through this tutorial, you’ll need the following:
- Node >= 6 installed on your machine
- npm >= 5.2 installed on your machine
- npx installed on your machine
Getting started
To get started, create a new React application by running this command:
npx create-react-app pusher-react-location
This creates a starter React project in a folder titled pusher-react-location
. To see the demo application at work, go to your terminal and run the command:
cd pusher-react-location
npm start
This serves the React application. Navigate your browser to http://locahost:3000
and you should get a view that looks like this:
Building the backend server
The backend server of our application will have the following functionality:
- Sharing users’ locations
- Authenticating new users on the presence channel
Create a /server
folder in your project:
mkdir server
cd server
Install the Node modules that we will need to power the backend server:
npm init -y
npm install express body-parser pusher
express
will power the web serverbody-parser
to handle incoming requestspusher
to add realtime functionality and online presence
Afterwards, create a server.js
file in the server/
directory:
touch server.js
Edit the server.js
file to look as follows:
// server/server.js
const express = require('express')
const bodyParser = require('body-parser')
const Pusher = require('pusher');
// create a express application
const app = express();
// initialize pusher
let pusher = new Pusher({
appId: 'PUSHER_APP_ID',
key: 'PUSHER_APP_KEY',
secret: 'PUSHER_APP_SECRET',
cluster: 'PUSHER_APP_CLUSTER',
encrypted: true
});
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
// to Allow CORS
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', '*');
res.header(
'Access-Control-Allow-Headers',
'Origin, X-Requested-With, Content-Type, Accept'
);
next();
});
[...]
We include the necessary JavaScript libraries and then create a new Pusher object using your
Pusher application credentials.
To obtain your Pusher credentials, create a new account here. Afterwards, you’ll be redirected to your Pusher dashboard. Go ahead and create a new project, obtain your
PUSHER_APP_ID
,PUSHER_APP_KEY
,PUSHER_APP_SECRET
,PUSHER_APP_CLUSTER
and add them to yourserver.js
file.
Afterwards, we specify some application middleware to handle incoming requests. The backend server will have two routes:
/pusher/auth
- handles requests to authenticate users joining the presence channel/update-location
- handles requests to trigger an event when a user updates their location.
With Pusher, when a new client tries to join a presence channel, a POST
request is first made to authenticate the new client. In this case, we create a random string to identify the client and this makes up the presenceData
object. The presenceData
, channel
and socketId
are then passed to Pusher to authenticate the client.
The /update-location
route accepts the incoming data and then triggers a location-update
event to the presence-channel
.
In later parts of the article, we will see how the channel is created in our React application
Add the code below to your server/server.js
file:
// server/server.js
[...]
app.post('/pusher/auth', (req, res) => {
let socketId = req.body.socket_id;
let channel = req.body.channel_name;
random_string = Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 5);
let presenceData = {
user_id: random_string,
user_info: {
username: '@' + random_string,
}
};
let auth = pusher.authenticate(socketId, channel, presenceData);
res.send(auth);
});
app.post('/update-location', (req, res) => {
// trigger a new post event via pusher
pusher.trigger('presence-channel', 'location-update', {
'username': req.body.username,
'location': req.body.location
})
res.json({ 'status': 200 });
});
let port = 3128;
app.listen(port);
console.log('listening');
Now that the backend server is created, you can run it by entering the command:
node server.js
Now, let’s look at how to build the rest of the application.
Creating the interface
First of all, we need to get a simple map interface for the application and to do this, let’s use Google Maps JavaScript API. Follow this guide to obtain your Google Maps API key which we will use later in this application.
Installing necessary packages
To add all functionality to our application, we’ll need to install the following packages:
- google-map-react - to use the Google Maps JavaScript API with react
- pusher-js - to enable realtime functionality with Pusher
- axios - to make POST requests to our backend server
- react-toastify - to notify users when new users are online or go offline
To install, go to the root folder of your react application and run the following in your terminal:
npm install google-map-react pusher-js axios react-toastify
Displaying the map
To see it at work in your application, edit your src/App.js
file to look like this:
// src/App.js
import React, { Component } from 'react';
import GoogleMap from 'google-map-react';
const mapStyles = {
width: '100%',
height: '100%'
}
const markerStyle = {
height: '50px',
width: '50px',
marginTop: '-50px'
}
const imgStyle = {
height: '100%'
}
const Marker = ({ title }) => (
<div style={markerStyle}>
<img style={imgStyle} src="https://res.cloudinary.com/og-tech/image/upload/s--OpSJXuvZ--/v1545236805/map-marker_hfipes.png" alt={title} />
<h3>{title}</h3>
</div>
);
class App extends Component {
render() {
return (
<div >
<GoogleMap
style={mapStyles}
bootstrapURLKeys={{ key: 'GOOGLE_MAPS_API_KEY' }}
center={{ lat: 5.6219868, lng: -0.1733074 }}
zoom={14}
>
<Marker
title={'Current Location'}
lat={5.6219868}
lng={-0.1733074}
>
</Marker>
</GoogleMap>
</div>
)
}
}
export default App;
In the App.js
file, we defined the center
of the map and a single marker which will represent the location of the user when they open the application.
Note: Remember to add your
GOOGLE_MAPS_API_KEY
which you can obtain here.
Now, when you run the application and navigate to localhost:3000
in your browser you get the view below:
Currently, the center of the map and the user’s pin are hard-coded into the application. Let’s look at how to make these dynamic and display the user’s location and the location of other users signed in to the application.
Displaying online friends locations in realtime with Pusher
Now let’s update the App.js
to include the functionality. First, we need to add states to our component that will track:
- The map center
- Users online
- Username of the current user
- Location for other online users
Update the App.js
file so that your constructor will look like this:
// src/App.js
[...]
import axios from 'axios';
import Pusher from 'pusher-js';
import { ToastContainer, toast } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';
class App extends Component {
constructor(props) {
super(props)
this.state = {
center: { lat: 5.6219868, lng: -0.23223 },
locations: {},
users_online: [],
current_user: ''
}
}
[...]
}
We then create a new Pusher object in the componentDidMount()
lifecycle method by specifying the PUSHER_APP_KEY
, PUSHER_APP_CLUSTER
and the authEndpoint
created on our backend server earlier in the article. Afterwards, we subscribe to the presence-channel
and then bind the channel to listen for four events:
pusher:subscription_succeeded
event that is triggered from the backend server when a user successfully subscribes to a presence channel .location-update
event which is triggered when another user’s location is updated.pusher:member_removed
event that is triggered when another user goes offline.pusher:member_added
event that is triggered when a new user comes online.
// src/App.js
[...]
class App extends Component {
[...]
componentDidMount() {
let pusher = new Pusher('PUSHER_APP_KEY', {
authEndpoint: "http://localhost:3128/pusher/auth",
cluster: "mt1"
})
this.presenceChannel = pusher.subscribe('presence-channel');
this.presenceChannel.bind('pusher:subscription_succeeded', members => {
this.setState({
users_online: members.members,
current_user: members.myID
});
this.getLocation();
this.notify();
})
this.presenceChannel.bind('location-update', body => {
this.setState((prevState, props) => {
const newState = { ...prevState }
newState.locations[`${body.username}`] = body.location;
return newState;
});
});
this.presenceChannel.bind('pusher:member_removed', member => {
this.setState((prevState, props) => {
const newState = { ...prevState };
// remove member location once they go offline
delete newState.locations[`${member.id}`];
// delete member from the list of online users
delete newState.users_online[`${member.id}`];
return newState;
})
this.notify()
})
this.presenceChannel.bind('pusher:member_added', member => {
this.notify();
})
}
[...]
}
Notice that we called a notify()
method on our Pusher events. Add the function to your App.js
file like below:
// src/App.js
class App extends Component {
[...]
notify = () => toast(`Users online : ${Object.keys(this.state.users_online).length}`, {
position: "top-right",
autoClose: 3000,
hideProgressBar: false,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
type: 'info'
});
[...]
}
We also called a getLocation()
method in the componentDidMount()
. This function is responsible for fetching the location of the user from the browser. Let’s take a look at how this works. Add the getLocation()
function to your App.js
file:
// src/App.js
class App extends Component {
[...]
getLocation = () => {
if ("geolocation" in navigator) {
navigator.geolocation.watchPosition(position => {
let location = { lat: position.coords.latitude, lng: position.coords.longitude };
this.setState((prevState, props) => {
let newState = { ...prevState };
newState.center = location;
newState.locations[`${prevState.current_user}`] = location;
return newState;
});
axios.post("http://localhost:3128/update-location", {
username: this.state.current_user,
location: location
}).then(res => {
if (res.status === 200) {
console.log("new location updated successfully");
}
});
})
} else {
alert("Sorry, geolocation is not available on your device. You need that to use this app");
}
}
[...]
}
The getLocation()
method first checks if the application can access the geolocation property of the browser and alerts the user if it can’t. The navigator.geolocation.watchPosition()
method gets the users’ location as the user moves and then updates the component states with the most up to date location of the user. Afterwards, a request is made to the backend server to trigger a location-update
event so that other signed in users can be notified with the latest location.
Finally, to show all locations of online users, let’s update the render()
function of the component to look like this:
// src/App.js
[...]
class App extends Component {
[...]
render() {
let locationMarkers = Object.keys(this.state.locations).map((username, id) => {
return (
<Marker
key={id}
title={`${username === this.state.current_user ? 'My location' : username + "'s location"}`}
lat={this.state.locations[`${username}`].lat}
lng={this.state.locations[`${username}`].lng}
>
</Marker>
);
});
return (
<div >
<GoogleMap
style={mapStyles}
bootstrapURLKeys={{ key: 'GOOGLE_MAPS_API_KEY' }}
center={this.state.center}
zoom={14}
>
{locationMarkers}
</GoogleMap>
</div>
)
}
[...]
}
locationMarkers
creates a list of Marker
’s for each of the online users. This will give the user a perspective of where his other online friends are.
Now, reload the application and navigate to localhost:3000
. Your application should work like this when multiple users are online:
Conclusion
In this tutorial, we saw how to use Pusher Channels, Google Maps and React to build a live map with online presence that lets you know where your friends online are. This tutorial is one of the many ways you can use Pusher Channels in the product you build. Feel free to use the concepts shared here in your own application. Here’s a link to the GitHub repository.
11 February 2019
by Oreoluwa Ogundipe