Introduction

This tutorial shows how to add a web map based on the HERE Maps API into an application based on the React framework.

Pre-Reqs

  • A computer
  • Node >= 8

That's it!

Setting up a Template

Let's start with a template React application using React's official create-react-app utility:

npm i -g create-react-app
create-react-app here-react-intro

If you're using a version of npm greater than 5.2.0, you may alternatively use the npx utility — built into npm — to avoid manually globally installing it:

npx create-react-app here-react-intro

Once the utility has finished running, you'll have a basic React project setup and ready to go.

Running npm start at this stage will automatically open your default browser to localhost:3000, which should yeild the following screen:

The initial create-react-app screen
Figure 1. The initial create-react-app screen

The Structure

If you look into the folder you generated the template within (if you copied+pasted the code above, the folder would be called here-react-intro), you'll see a few files and folders present.

Standard Files

  • node_modules - Any JavaScript developer will be familiar with this one. It's the location of your code's installed depencies
  • package.json - The file used to describe some metadata about the project used for package management. This includes the list of dependencies this project has that are installed by npm
  • README.md - This is the file that will show up as the default documentation page if you end up using one of the various remote git tracking software to track your code (this includes GitLab, GitHub, and many others). By default, this file includes some information about how to run your code, debug it, and others

Template Specific Files

  • public - This is where the files that are staticly hosted on the site. EG, if you place a test.html file in this directory, it will appear in your built code under /test.html - favicon.ico - The favicon to be associated with the site
    • index.html - The HTML file to be loaded when the page intially is loaded in the browser. React is called preon top of this file and loads the top level component into the <div id="root"></div> div that's present in this file. We can modify this file to include external library imports from the CDN, and more
  • src - The folder that contains all of the React codebase
    • App.js - The top level (and currently only) component in this codebase
    • App.css - A styling file that only applies to the App component
    • index.js - The file that's intially loaded. This includes the call to render the App component into the DOM: ReactDOM.render(<App />, document.getElementById('root'));
    • index.css - Global styling file. This file is imported into index.js and applies to all code

Setting Up The HERE API

If we follow the JavaScript API quick start guide, we can integrate a map into our React component. That said, we'll need to make some mild adjustments to the instructions to adapt to the way React works, so let's go step-by-step.

Loading the API Code Libraries

Like mentioned in the quick starting guide, we need to load the core and service modules. These script tags are still added to the head of the document, same as the guide there, but we'll put them in the /public/index.html file. We can also add the meta-tag to optimize the performance for mobile at the same time:

<head>
  ...
  <title>React App</title>
  <meta name="viewport" content="initial-scale=1.0, width=device-width" />
  <script
    type="text/javascript"
    src="https://js.api.here.com/v3/3.1/mapsjs-core.js"
  ></script>
  <script
    type="text/javascript"
    src="https://js.api.here.com/v3/3.1/mapsjs-service.js"
  ></script>
</head>
Creating the reusable Map component

Now that we have loaded the HERE Map Libraries, lets create a reusable Map component by creating a file src/DisplayMapClass.js and add the following code:

// src/DisplayMapClass.js
import * as React from 'react';

export class DisplayMapClass extends React.Component {
  mapRef = React.createRef();

  state = {
    // The map instance to use during cleanup
    map: null
  };

  componentDidMount() {

    const H = window.H;
    const platform = new H.service.Platform({
        apikey: "{HERE-API-KEY}"
    });

    const defaultLayers = platform.createDefaultLayers();

    // Create an instance of the map
    const map = new H.Map(
      this.mapRef.current,
      defaultLayers.vector.normal.map,
      {
        // This map is centered over Europe
        center: { lat: 50, lng: 5 },
        zoom: 4,
        pixelRatio: window.devicePixelRatio || 1
      }
    );

    this.setState({ map });
  }

  componentWillUnmount() {
    // Cleanup after the map to avoid memory leaks when this component exits the page
    this.state.map.dispose();
  }

  render() {
    return (
      // Set a height on the map so it will display
      <div ref={this.mapRef} style={{ height: "500px" }} />
    );
  }
}

Now, lets import the DisplayMapClass.js component in the App.js to render the map.

//App.js
import React from 'react';
import DisplayMapClass from './DisplayMapClass';

function App() {
return (
<DisplayMapClass />
);
}
export default App;

Loading the page should yeild a default render of a map, courtousy of the HERE JS SDK.

A default embedded HERE map of Europe
Figure 2. A default embedded HERE map of Europe

Adding Interactivity

While the previous map may suffice with your business needs, many apps require interactivity. Luckily, the JS SDK gives us ways to add this functionality trivially.

We'll first start by adding two new imports to our public/index.html file to import the files from the HERE SDK required for it's related features to function:

<head>
  ...
  <title>React App</title>
  <meta name="viewport" content="initial-scale=1.0, width=device-width" />
  <script
    type="text/javascript"
    src="https://js.api.here.com/v3/3.1/mapsjs-core.js"
  ></script>
  <script
    type="text/javascript"
    src="https://js.api.here.com/v3/3.1/mapsjs-service.js"
  ></script>
  <script
    type="text/javascript"
    src="https://js.api.here.com/v3/3.1/mapsjs-ui.js"
  ></script>
  <script
    type="text/javascript"
    src="https://js.api.here.com/v3/3.1/mapsjs-mapevents.js"
  ></script>
</head>

Then, we can use the new functionality enabled by those imports by making some minor changes to the component we've already built.

export default class DisplayMapClass extends React.Component {
  mapRef = React.createRef();
  state = {
    map: null
  };

  componentDidMount() {
    const H = window.H;
    const platform = new H.service.Platform({
        apikey: "{HERE-API-KEY}"
    });

    const defaultLayers = platform.createDefaultLayers();

    const map = new H.Map(
      this.mapRef.current,
      defaultLayers.vector.normal.map,
      {
        center: { lat: 50, lng: 5 },
        zoom: 4,
        pixelRatio: window.devicePixelRatio || 1
      }
    );

    // MapEvents enables the event system
    // Behavior implements default interactions for pan/zoom (also on mobile touch environments)
    // This variable is unused and is present for explanatory purposes
    const behavior = new H.mapevents.Behavior(new H.mapevents.MapEvents(map));

    // Create the default UI components to allow the user to interact with them
    // This variable is unused
    const ui = H.ui.UI.createDefault(map, defaultLayers);

    this.setState({ map });
  }

  componentWillUnmount() {
    this.state.map.dispose();
  }

  render() {
    return <div ref={this.mapRef} style={{ height: "500px" }} />;
  }
}

Now we should be able to zoom in and out of maps, pan to locations of our choosing, and even have a convinient API present for our users to utilize

A gif of zooming in and panning of a map into London
Figure 3. A gif of zooming in and panning of a map into London

With the new features in the newly announced HERE JavaScript SDK 3.1 release, we can also zoom into a large city, then hold the alt or option key while dragging to tilt our view of the city to see the awesome new extruded building view! 😍

A gif of zooming further into the Tower of London, and tilting the view to see the extruded buildings
Figure 4. A gif of zooming further into the Tower of London, and tilting the view to see the extruded buildings

A Note on Hooks

Okay, so you've heard about hooks, possibly even used them. They were the new hotness in the React 16.8.0 release. An example of the previous component using hooks is-as-follows:

// src/DisplayMapFC.js

export const DisplayMapFC = () => {
  // Create a reference to the HTML element we want to put the map on
  const mapRef = React.useRef(null);

  /**
   * Create the map instane
   * While `useEffect` could also be used here, `useLayoutEffect` will render
   * the map sooner
   */
  React.useLayoutEffect(() => {
    // `mapRef.current` will be `undefined` when this hook first runs; edgecase that
    if (!mapRef.current) return;
    const H = window.H;
    const platform = new H.service.Platform({
        apikey: "{HERE-API-KEY}"
    });
    const defaultLayers = platform.createDefaultLayers();
    const hMap = new H.Map(mapRef.current, defaultLayers.vector.normal.map, {
      center: { lat: 50, lng: 5 },
      zoom: 4,
      pixelRatio: window.devicePixelRatio || 1
    });

    const behavior = new H.mapevents.Behavior(new H.mapevents.MapEvents(hMap));

    const ui = H.ui.UI.createDefault(hMap, defaultLayers);

    // This will act as a cleanup to run once this hook runs again.
    // This includes when the component unmounds
    return () => {
      hMap.dispose();
    };
  }, [mapRef]); // This will run this hook every time this ref is updated

  return <div className="map" ref={mapRef} style={{ height: "500px" }} />;
};

Keep in mind that while both useEffect and useLayoutEffect will work here, if you wanted to use useMemo to keep hMap as a local variable, it wouldn't render due to the timing of that hook:

Remember that the function passed to useMemo runs during rendering.

- Official React Hooks API

For that reason, it's suggested to use a useState and set the variable value within the useEffect or useLayoutEffect hook

Code Examples

You can see the differences in loading the FC and the Class versions by using the code above and placing them into the src/App.js file as such:

import React from 'react';
import {DisplayMapClass} from "./DisplayMapClass";
import {DisplayMapFC} from "./DisplayMapFC";

function App() {
  return (
    <>
      {/*<DisplayMapClass/>*/}
      <DisplayMapFC/>
    </>
  );
}

export default App;

results matching ""

    No results matching ""