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>

Initialize Communication with Back-end Services

We'll need the ability to communicate with the HERE REST APIs from our React codebase. Because we're using ES6 modules, let's go ahead an create a new file /src/platform.js and add the code to allow communication there:

export const platform = new H.service.Platform({
  apikey: "{YOUR_APIKEY}"
});

Now, if we have any file or component we need to use platform in, we can use import {platform} from './platform' to retrieve it from anywhere in the src folder.

Initialize the Map

The next step in the getting started guide references the H.Map constructor to load in a map. The first argument in that constructor is an element reference. When using vanilla JS, this is simple enough to do by using document.getElementById and an HTML with an associated ID. Since React has the ability to dyntamically load elements onto the page, we want to allow React itself to provide us a reference to an HTML element. This can be done using refs.

React Refs

Let's use a class-based component for a quick example:

// /src/ConsoleLogRefComp.js
import * as React from 'react'; // This is used to allow JSX in the file

export class ConsoleLogRefComp extends React.Component {
  refVar = React.createRef();

  componentDidMount() {
    console.log(this.refVar.current);
  }

  render() {
    return <p ref={this.refVar}>Hello, World!</p>;
  }
}

Will render the paragraph element with the text Hello, World to the screen once you add it to your src/App.js file.

// src/App.js

import React from 'react';
import {ConsoleLogRefComp} from "./ConsoleLogRefComp";

function App() {
  return (
    <>
      <ConsoleLogRefComp/>
    </>
  );
}

export default App;

Once it renders, if you open your debugger and hover over the console.logged value, you'll see that we've gotten a reference to the underlying HTML element.

"Hello, World" being rendered to the screen while the `console.log` value is being highlighted in the Chrome console - outlining the element visually to show their association
Figure 2. "Hello, World" being rendered to the screen while the `console.log` value is being highlighted in the Chrome console - outlining the element visually to show their association

We only have access to this HTML element after it renders to the screen, which is why we have our console.log in a componentDidMount lifecycle method.

Rendering A Map to a Ref

Now that we have access to the underlying element, we can pass it to our H.Map constructor to create a new instance of the map:

// src/DisplayMapClass.js
import * as React from 'react';
import {platform} from "./platform";

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

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

  componentDidMount() {
    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" }} />
    );
  }
}

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 3. A default embedded HERE map of Europe

Adding Interactivity

While the previous map may suffice with your buisness 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 class DisplayMapClass extends React.Component {
  mapRef = React.createRef();
  state = {
    map: null
  };

  componentDidMount() {
    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 4. 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 5. 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 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 ""