Working with React
This article describes how to use the HERE Maps API for JavaScript with React. The target is to demonstrate how to build a React component that displays the map and responds to the actions of the user, be it direct interaction with the map or the other components.
Setup
For the fast setup of the new React application we will use the Create React App environment. It provides a fast way to get started building a new single-page application. Execute the npx
runner as below (it requires Node >= 8.10 and npm >= 5.6):
npx create-react-app jsapi-react && cd jsapi-react
The call above produces the scaffolding needed to start the application. The directory structure in the jsapi-react
directory looks as follows. The React components reside in the src
directory:
my-app
├── README.md
├── node_modules
├── package.json
├── .gitignore
├── public
│ ├── ...
└── src
├── App.css
├── App.js
├── App.test.js
├── index.css
├── index.js
├── logo.svg
└── serviceWorker.js
└── setupTests.js
Note
The latest version of create-react-app
uses Webpack 5 and is configured to use the polyfill for the global variables. That default does not work with the HERE Maps API for JavaScript. In order to change the setting eject the react app:
npm run eject
And add the following option to the Webpack configuration object under config/webpack.config.js
:
node: {
global: false
}
The recommended way to use HERE Maps API for JavaScript within this environment is to install maps-api-for-javascript
NPM package which is hosted at https://repo.platform.here.com/. Add a registry entry to the NPM configuration by executing the following command:
npm config set @here:registry https://repo.platform.here.com/artifactory/api/npm/maps-api-for-javascript/
After that the package from the @here
namespace can be installed as usual:
npm install @here/maps-api-for-javascript --save
At this step the environment setup is complete, all packages needed to build a sample application are installed, and it is possible to start the development server by executing:
npm start
The command above launches the development server with the "hot reload" functionality and opens the application in the browser.
Add a static map component
It is possible to add a static map to the application by creating a component that contains an H.Map
instance and renders it to the component's container. In order to do that create a file Map.js
in the src
directory for the new React class component. This component displays a map in the container that has a width and a height of 300 pixels:
import React from 'react';
import H from "@here/maps-api-for-javascript";
export default class Map extends React.Component {
constructor(props) {
super(props);
this.ref = React.createRef();
this.map = null;
}
componentDidMount() {
if (!this.map) {
const platform = new H.service.Platform({
apikey: '{YOUR_API_KEY}'
});
const layers = platform.createDefaultLayers();
const map = new H.Map(
this.ref.current,
layers.vector.normal.map,
{
pixelRatio: window.devicePixelRatio,
center: {lat: 0, lng: 0},
zoom: 2,
},
);
this.map = map;
}
}
render() {
return (
<div
style={{ width: '300px', height:'300px' }}
ref={this.ref}
/>
)
}
}
The component above now can be used within the App
component, replace the content of th src/App.js
with the following code:
import Map from './Map';
function App() {
return (
<div>
<Map />
</div>
);
}
export default App;
That will render the static map at the zoom level 2 and 0 latitude and longitude: 
Resizing the map
In many cases it is desirable that the map occupies the full width and/or height of the component. The H.Map
instance does not attempt to deduce when the parent container is resized and the map needs an explicit resize()
method call in order to adjust to the new dimensions of the container. To demonstrate how it can be achieved within the component we will use the simple-element-resize-detector
. Run the following command from the project's directory:
npm install simple-element-resize-detector --save
In the src/Map.js
adjust the import statements by importing the simple-element-resize-detector
library:
import React from 'react';
import H from "@here/maps-api-for-javascript";
import onResize from 'simple-element-resize-detector';
Update componentDidMount
method with the map.getViewPort().resize()
call and adjust the width of the container in the render
method:
componentDidMount() {
if (!this.map) {
const platform = new H.service.Platform({
apikey: '{YOUR_API_KEY}'
});
const layers = platform.createDefaultLayers();
const map = new H.Map(
this.ref.current,
layers.vector.normal.map,
{
pixelRatio: window.devicePixelRatio,
center: {lat: 0, lng: 0},
zoom: 2,
},
);
onResize(this.ref.current, () => {
map.getViewPort().resize();
});
this.map = map;
}
}
render() {
return (
<div
style={{ position: 'relative', width: '100%', height:'300px' }}
ref={this.ref}
/>
)
}
the component will assume the width of the enclosing container: 
Setting the zoom and center
We want another component to take a user's input and change the zoom level and the center of the map. The MapPosition component (src/MapPosition.js
) has three input fields and takes a callback from the parent as props
:
import React from 'react';
export default class MapPosition extends React.Component {
handleOnChange = (ev) => {
const {
onChange
} = this.props;
onChange(ev.target.name, ev.target.value);
}
render() {
const {
lat,
lng,
zoom
} = this.props;
return (
<>
<div>
Zoom:
<input
onChange={this.handleOnChange}
name="zoom"
type="number"
value={zoom}
/>
</div>
<div>
Latitude:
<input
onChange={this.handleOnChange}
name="lat"
type="number"
value={lat}
/>
</div>
<div>
Longitude:
<input
onChange={this.handleOnChange}
name="lng"
type="number"
value={lng}
/>
</div>
</>
)
}
}
It communicates with the App
component which manages the state of the application. The App
component accordingly should be updated to store the state, and pass the appropriate props
to the child components:
import React from 'react';
import Map from './Map';
import MapPosition from './MapPosition';
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
zoom: 0,
lat: 0,
lng: 0
}
}
handleInputChange = (name, value) => {
this.setState({
[name]: value
})
}
render() {
const {
zoom,
lat,
lng
} = this.state;
return (
<div className="App">
<Map
lat={lat}
lng={lng}
zoom={zoom}
/>
<MapPosition
lat={lat}
lng={lng}
onChange={this.handleInputChange}
zoom={zoom}
/>
</div>
);
}
}
The final step is to adjust the Map
component by adding the componentDidUpdate
method responsible for updating the map view as per the user's input:
componentDidUpdate() {
const {
lat,
lng,
zoom
} = this.props;
if (this.map) {
clearTimeout(this.timeout);
this.timeout = setTimeout(() => {
this.map.setZoom(zoom);
this.map.setCenter({lat, lng});
}, 100);
}
}
The resulting application can take the input from the user with the help of the MapPosition
component, store the state in the App
and update the Map
as per the user input: 
The interactive map
The application above takes only the input via the MapPosition
component, normally users expect the map itself to be interactive. The optimal solution enables the user to input the values directly and see the desired location on the map, as well as interact with the map and see the current coordinates. That can be achieved by adding the mapviewchange
listener to the H.Map
instance and updating the App
state via the callback. In order to achieve that add a method in src/App.js
that will be responsible to update the App
state:
handleMapViewChange = (zoom, lat, lng) => {
this.setState({
lat,
lng,
zoom
})
}
And pass it as props
to the Map
component:
<Map
lat={lat}
lng={lng}
onMapViewChange={this.handleMapViewChange}
zoom={zoom}
/>
In the Map
component add a handleMapViewChange
method. The method receives the map event and calls the onMapViewChange
callback:
handleMapViewChange = (ev) => {
const {
onMapViewChange
} = this.props;
if (ev.newValue && ev.newValue.lookAt) {
const lookAt = ev.newValue.lookAt;
const lat = Math.trunc(lookAt.position.lat * 1E7) / 1E7;
const lng = Math.trunc(lookAt.position.lng * 1E7) / 1E7;
const zoom = Math.trunc(lookAt.zoom * 1E2) / 1E2;
onMapViewChange(zoom, lat, lng);
}
}
Add the following lines to attach a listener and enable the interactive behaviour after the map instantiation in the componentDidMount
:
map.addEventListener('mapviewchange', this.handleMapViewChange);
new H.mapevents.Behavior(new H.mapevents.MapEvents(map));
Besides that, to avoid adverse effects, it is important to remove the event listeners in the componentWillUnmount
:
componentWillUnmount() {
if (this.map) {
this.map.removeEventListener('mapviewchange', this.handleMapViewChange);
}
}