🎉 New! Web Push Notifications for Chatkit. Learn more in our latest blog post.
Hide
Products
chatkit_full-logo

Extensible API for in-app chat

channels_full-logo

Build scalable realtime features

beams_full-logo

Programmatic push notifications

Developers

Docs

Read the docs to learn how to use our products

Tutorials

Explore our tutorials to build apps with Pusher products

Support

Reach out to our support team for help and advice

Sign in
Sign up

Build a live map application with React

  • Oreoluwa Ogundipe

February 11th, 2019
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 server
  • body-parser to handle incoming requests
  • pusher 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 your server.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 presenceDataobject. 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.

Clone the project repository
  • JavaScript
  • Location
  • Maps
  • Node.js
  • Online Presence
  • React
  • Channels

Products

  • Channels
  • Chatkit
  • Beams

© 2019 Pusher Ltd. All rights reserved.

Pusher Limited is a company registered in England and Wales (No. 07489873) whose registered office is at 160 Old Street, London, EC1V 9BW.