24
Interactive Maps Where You Can Pick a Style or Theme with React
For this project, I want to display an interactive map that allows the user to choose a theme. A slippy map like this that allows the user to pan and zoom around is one of the most common maps on the web. Since it may not be straightforward how to fetch raster tiles and build the standard behaviors into a UI, using the Maps JavaScript SDK is invaluable for a consistent experience.
By clicking one of the thumbnail images, the interactive map will update with a new tile service provider as demonstrated here:
For a basic single-page app, you might start by including the React and HERE libraries from a CDN directly in your index.html.
<script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script>
Create a simple ES6 class called SimpleHereMap
. The componentDidMount()
method runs after the render()
method per the React Component Lifecycle which means we can more or less include the HERE JavaScript Quick Start code just as is.
const e = React.createElement;
class SimpleHereMap extends React.Component {
componentDidMount() {
var platform = new H.service.Platform({
app_id: 'APP_ID_HERE',
app_code: 'APP_CODE_HERE',
})
var layers = platform.createDefaultLayers();
var map = new H.Map(
document.getElementById('map'),
layers.normal.map,
{
center: {lat: 42.345978, lng: -83.0405},
zoom: 12,
});
var events = new H.mapevents.MapEvents(map);
var behavior = new H.mapevents.Behavior(events);
var ui = H.ui.UI.createDefault(map, layers);
}
render() {
return e('div', {"id": "map"});
}
}
const domContainer = document.querySelector('#app');
ReactDOM.render(e(SimpleHereMap), domContainer);
This example works if you use it standalone in a single index.html file but doesn’t make use of JSX and falls apart if you try to use create-react-app
. If you use that tool as described in a few of the other ReactJS posts you may see the next error.
Adapting the above example for create-react-app
requires a few minor changes.
- Move the includes of the HERE script libraries into public/index.html
- Create a Map.js with the SimpleHereMap class.
- Update the
render()
method to use JSX to place the element.
If you make those changes and npm start
you will likely see the following error in your console:
‘H’ is not defined no-undef
The initialization of H.service.Platform()
is causing an error because H is not in scope. This is not unique to HERE and is generally the case with any 3rd party code you try to include with React. Using create-react-app
implies using its toolchain including webpack as a module bundler, eslint for checking syntax, and Babel to transpile JSX.
Any library like the HERE JavaScript SDK that has a global variable like H might run into a similar problem during compilation (jQuery, Leaflet, etc.). By referencing non-imported code like this, the syntax linter which is platform agnostic will complain because it doesn’t know that the page will ultimately be rendered in a web browser.
The simple fix is to reference window.H
instead. Unfortunately, this does violate one of the basic principles of building modular JavaScript applications by tightly coupling our <script>
includes with our component but it works.
The script libraries are simply included in the public index.html.
@@ -4,6 +4,14 @@
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
+
+ <link rel="stylesheet" type="text/css" href="https://js.api.here.com/v3/3.0/mapsjs-ui.css?dp-version=1526040296" />
+
+ <script type="text/javascript" src="https://js.api.here.com/v3/3.0/mapsjs-core.js"></script>
+ <script type="text/javascript" src="https://js.api.here.com/v3/3.0/mapsjs-service.js"></script>
+ <script type="text/javascript" src="https://js.api.here.com/v3/3.0/mapsjs-ui.js"></script>
+ <script type="text/javascript" src="https://js.api.here.com/v3/3.0/mapsjs-mapevents.js"></script>
+
The Map component defines the rendered map. We’ll be making a few more changes to this class later once we get to the theme selection. We're storing a lot of the properties like lat, long, zoom, and app credentials as state so that they can be changed dynamically.
class Map extends Component {
constructor(props) {
super(props);
this.platform = null;
this.map = null;
this.state = {
app_id: props.app_id,
app_code: props.app_code,
center: {
lat: props.lat,
lng: props.lng,
},
zoom: props.zoom,
theme: props.theme,
style: props.style,
}
}
// TODO: Add theme selection discussed later HERE
componentDidMount() {
this.platform = new window.H.service.Platform(this.state);
var layer = this.platform.createDefaultLayers();
var container = document.getElementById('here-map');
this.map = new window.H.Map(container, layer.normal.map, {
center: this.state.center,
zoom: this.state.zoom,
})
var events = new window.H.mapevents.MapEvents(this.map);
// eslint-disable-next-line
var behavior = new window.H.mapevents.Behavior(events);
// eslint-disable-next-line
var ui = new window.H.ui.UI.createDefault(this.map, layer)
}
render() {
return (
<div id="here-map" style={{width: '100%', height: '400px', background: 'grey' }} />
);
}
}
At this point though we have a working and extensible ReactJS component that is ready to display a HERE Interactive Maps.
Since a map can be an extension to a brand or preferences, there are many themes and styles available for how to present a map on a page. The following image depicts some of the examples of maps you can use from the Maps Tile API.
The src/ThemeSelector.js component is simply intended to provide a listing of thumbnail images the user can choose from. It includes some of the more popular themes:
class ThemeSelector extends Component {
render() {
var themes = [
'normal.day',
'normal.day.grey',
'normal.day.transit',
'normal.night',
'normal.night.grey',
'reduced.night',
'reduced.day',
'pedestrian.day',
'pedestrian.night',
];
var thumbnails = [];
var onChange = this.props.changeTheme;
themes.forEach(function(theme) {
thumbnails.push(<img key={ theme } src={ 'images/' + theme + '.thumb.png' } onClick= { onChange } alt={ theme } id={ theme } />);
});
return (
<div>
{ thumbnails }
</div>
);
}
}
To make the click event work, we’re going to add a bit more to our src/Map.js component. The changeTheme
method described below is an example like you’d find for most any HERE JavaScript implementation.
changeTheme(theme, style) {
var tiles = this.platform.getMapTileService({'type': 'base'});
var layer = tiles.createTileLayer(
'maptile',
theme,
256,
'png',
{'style': style}
);
this.map.setBaseLayer(layer);
}
We will call this method from the shouldComponentUpdate()
method. From the React Component Lifecycle, this method is called when state changes occur in order to determine if it’s necessary to re-render the component. When we select a new theme, we call the setBaseLayer
method and can update the map without requiring React to make a more costly re-render of the entire DOM.
shouldComponentUpdate(props, state) {
this.changeTheme(props.theme, props.style);
return false;
}
Putting it all together, we use src/App.js to track state for the theme selection as the common ancestor to both the Map and ThemeSelector components.
The source code looks like this:
import Map from './Map.js';
import ThemeSelector from './ThemeSelector.js';
class App extends Component {
constructor(props) {
super(props);
this.state = {
theme: 'normal.day',
}
this.onChange = this.onChange.bind(this);
}
onChange(evt) {
evt.preventDefault();
var change = evt.target.id;
this.setState({
"theme": change,
});
}
render() {
return (
<div className="App">
<SimpleHereMap
app_id="APP_ID_HERE"
app_code="APP_CODE_HERE"
lat="42.345978"
lng="-83.0405"
zoom="12"
theme={ this.state.theme }
/>
<ThemeSelector changeTheme={ this.onChange } />
</div>
);
}
}
Ideally we’d like to include an npm
package that encapsulates the HERE Map APIs as React components for use in our applications. There are some community projects to create these packages but your experience may vary depending on which one you choose to use. It would be good to know what you’ve used successfully, so leave a note in the comments.
For everybody else just looking for a quick way to get compatibility with many of the other JavaScript API examples hopefully the window.H trick is what you were looking for.
You can find the source code for this project on GitHub.
24