Using OpenLayers with React
Published
Updated Guide Available
I published an updated guide on how to use OpenLayers with React Functional Components and Hooks. Check it out here: https://taylor.callsen.me/using-openlayers-with-react-functional-components/
Flux Data Flow and Concepts
React and Flux are a great way to produce complex web applications while maintaining a simple and consistent data flow under the hood. When configured correctly, the UI and its various components will update on their own whenever their underlying data is changed.

Flux Data Flow based on Flux Basic Concepts Documentation
Data flows through out a Flux application in a single direction (notice the counter-clock wise arrows in the diagram above). The application’s single “true” source of data lives in the Flux Stores, and is communicated out to the various listening UI components anytime it is changed. These components simply consume data from the Stores, and never update it directly.
Flux Action calls are used when data within the Stores needs to be updated. Examples scenarios are things like new data being available from the backend, or when a user interaction occurs. Action calls can be executed within an AJAX response callback, or from inside a mouse click event handler. Placing an Action call in response to a click event is represented by the View to Action arrow above.
One invoked, that Action calls pass through the Dispatcher, and find their way to the Flux Store. Once they reach the store, they cause updates to the data within the Store. The newly updated data within the Store is emitted back out to the UI components, who will update or re-render based on this new data.
Integrating Open Layers with React
While it seems straight forward to achieve this pattern when working with React components, things get more complicated when working with external JavaScript libraries, especially ones that maintain their own Data Stores. Examples include the D3 charting library, OpenLayers, Google Maps, or any library where data has to be explicitly added or updated.
React provides several hooks that can be used to pass data into these external libraries whenever data within the Flux Stores is updated. Our example below will make use of the React Component Lifecycle componentDidUpdate function.

Flux Data Flow updated to include External Libraries
Code Example
To integrate an OpenLayers Map (and other external libraries) into our React / Flux pattern, we will create a standard “wrapper” Component to house the OpenLayers Map. The wrapper Component’s render function will create a div element in the DOM, which will contain our OpenLayers map.
class Map extends React.Component {
render () {
return (
<div ref="mapContainer"> </div>
);
}
}
module.exports = Map;
We need to make sure the container div has been rendered into the DOM before creating the Map. The wrapper Component’s componentDidMount() function executes after the wrapper Component’s render() function completes for the first time, which makes it the perfect place to create the OpenLayers map (since we know the map container div will have been rendered into the DOM).
Notice on line 18 below, we are using the React component’s Refs to retrieve the map container div (the call to this.refs.mapContainer). Once we create the OpenLayers Map and Vector Layer objects, we will save references to them into the wrapper Component’s state (see lines 36-39). Saving these references to state will allow us to interact with the external OpenLayers objects later on.
class Map extends React.Component {
//other functions eliminated for brevity
componentDidMount() {
// create feature layer and vector source
var featuresLayer = new ol.layer.Vector({
source: new ol.source.Vector({
features:[],
})
});
// create map object with feature layer
var map = new ol.Map({
target: this.refs.mapContainer,
layers: [
//default OSM layer
new ol.layer.Tile({
source: new ol.source.OSM()
}),
featuresLayer
],
view: new ol.View({
center: [-11718716.28195593, 4869217.172379018], //Boulder, CO
zoom: 13,
})
});
// save map and layer references to local state
this.setState({
map: map,
featuresLayer: featuresLayer
});
}
//other functions eliminated for brevity
}
module.exports = Map;
We will write logic in the wrapper Component’s componentDidUpdate() function to reference the OpenLayers Map and Vector Layer objects in state, and update these objects with new data anytime the wrapper Component is updated.
The new vector data is pushed onto the OpenLayers Map by replacing the Vector Layer’s Source object with the new routes data. In this example, routes are supplied by props, but they could of course come from Component state or directly from a Flux Store.
Keep in mind that the componentDidUpdate() function is executed every time the Component is updated (i.e. new props are passed, state is changed, etc.). A simple linkage like this means OpenLayers will have to redraw the Vector Layer every time the React Component is updated! See a later article about using libraries like Immutable JS to prevent excessively updating external libraries.
class Map extends React.Component {
// other functions eliminated for brevity
// pass new features from props into the OpenLayers layer object
componentDidUpdate(prevProps, prevState) {
this.state.featuresLayer.setSource(
new ol.source.Vector({
features: this.props.routes
})
);
}
// other functions eliminated for brevity
}
module.exports = Map;
Finally, to allow user interactions and events within OpenLayers to hook back into our React/Flux app, we will set a click event handler on the OpenLayers Map. The event handler will fire off an Action call to notify the Flux Store of the event, and include data about the event (which in our case is the clicked locations on the Map). The Action call is placed on line 28 below, with various steps being taken before it to convert the clicked location into WKT format.
At this point the Flux Data Store is aware of the UI click Interaction (and the location of the click) even though it occurred within OpenLayers. The Flux Data Store can respond to this interaction, and emit updated data out to the rest of the application as needed.
class Map extends React.Component {
// other functions eliminated for brevity
componentDidMount() {
// placed at the bottom of the componentDidMount function
// (other function content elimintated for brevity)
map.on('click', this.handleMapClick.bind(this));
}
handleMapClick(event) {
// create WKT writer
var wktWriter = new ol.format.WKT();
// derive map coordinate (references map from Wrapper Component state)
var clickedCoordinate = this.state.map.getCoordinateFromPixel(event.pixel);
// create Point geometry from clicked coordinate
var clickedPointGeom = new ol.geom.Point( clickedCoordinate );
// write Point geometry to WKT with wktWriter
var clickedPointWkt = wktWriter.writeGeometry( clickedPointGeom );
// place Flux Action call to notify Store map coordinate was clicked
Actions.setRoutingCoord( clickedPointWkt );
}
// other functions eliminated for brevity
}
module.exports = Map;
Wrap up
The methodology above can be used to easily integrate data and interactions between a React/Flux application and an external library such as Open Layers 3. See this article for optimizations that can be made to the shouldComponentUpdate() and componentDidUpdate() functions of our Component to prevent triggering excess re-renders inside these external libraries. The complete Wrapper component discussed in our example is assembled below. Hope this article was helpful!
//externals
import ReactDOM from 'react-dom';
import React from 'react';
//open layers and styles
var ol = require('openlayers');
require('openlayers/css/ol.css');
class Map extends React.Component {
componentDidMount() {
// create feature layer and vector source
var featuresLayer = new ol.layer.Vector({
source: new ol.source.Vector({
features:[]
})
});
// create map object with feature layer
var map = new ol.Map({
target: this.refs.mapContainer,
layers: [
//default OSM layer
new ol.layer.Tile({
source: new ol.source.OSM()
}),
featuresLayer
],
view: new ol.View({
center: [-11718716.28195593, 4869217.172379018], //Boulder
zoom: 13,
})
});
map.on('click', this.handleMapClick.bind(this));
// save map and layer references to local state
this.setState({
map: map,
featuresLayer: featuresLayer
});
}
// pass new features from props into the OpenLayers layer object
componentDidUpdate(prevProps, prevState) {
this.state.featuresLayer.setSource(
new ol.source.Vector({
features: this.props.routes
})
);
}
handleMapClick(event) {
// create WKT writer
var wktWriter = new ol.format.WKT();
// derive map coordinate (references map from Wrapper Component state)
var clickedCoordinate = this.state.map.getCoordinateFromPixel(event.pixel);
// create Point geometry from clicked coordinate
var clickedPointGeom = new ol.geom.Point( clickedCoordinate );
// write Point geometry to WKT with wktWriter
var clickedPointWkt = wktWriter.writeGeometry( clickedPointGeom );
// place Flux Action call to notify Store map coordinate was clicked
Actions.setRoutingCoord( clickedPointWkt );
}
render () {
return (
<div ref="mapContainer"> </div>
);
}
}
module.exports = Map;
Comments
No responses yet