Sorry, you need to enable JavaScript to visit this website.

Adding TomTom Maps to a Modern React App

We’ll explore how to add TomTom web maps to a sample React application using Hooks and function components.


Mapping and location-based apps are exploding in popularity. It’s challenging to think of modern web apps and sites that don’t use maps somehow. Fortunately, it’s simple to add detailed, interactive maps to web apps using the TomTom Maps SDK for JavaScript.

As the most-used front-end framework, React seems to be a good choice for mapping-heavy web apps. React’s excellent performance is one reason it’s so popular — and it owes much of that performance to its virtual DOM. However, the virtual DOM has a disadvantage: it doesn’t always play nicely with mapping libraries that require access to actual (not virtual) DOM elements. 

React does offer a way to work around this limitation. In this article, we’ll explore how to use this workaround to add TomTom web maps to a sample React application. We’ll use the latest React best practices like Hooks and function components.

Getting Started

While there are many ways to create a React application, we’re building atop a sample app generated by Create React App (CRA) as it’s the de-facto standard. However, it’s okay if you prefer to use a different starter template or add maps to an existing application. The lessons you learn here work in any React application.

You can find the sample app’s code as well as a live version of the app on StackBlitz. Note that the API key you see in the code only works on this single Stackblitz URL. You’ll need to register a TomTom Developer Portal account if you’d like to create your own copy of the app on StackBlitz or run it on your own computer. If you visit the live version of the app, it should look like this:

Screen Shot 2021-04-13 at 11.14.59 AM

If you’re adding TomTom maps to an existing React app, first install the TomTom Maps SDK. Modern React apps use npm for build and dependency management, so run the following command in your React app’s root directory to install the SDK from npm:

npm install @tomtom-international/web-sdk-maps –save

Adding a TomTom Map

Now, let’s dive into the sample React app’s code to add a TomTom map. Near the top App.js component, we added two important imports:

import '@tomtom-international/web-sdk-maps/dist/maps.css'
import tt from '@tomtom-international/web-sdk-maps';

These lines import both the TomTom Maps JavaScript SDK and the default map stylesheet. 

A few lines below, you may observe that our entire application lives inside a functional component named App. We’ll step through the important parts one at a time and explain what’s going on.

First, there is a call to the useRef hook:

const mapElement = useRef();

Refs provide access to non-virtual DOM elements. If you’ve worked on older class-based React apps, you may be familiar with refs created by React.createRef.

We’ll revisit mapElement shortly, but first, let’s look at the code that comes immediately after it and contains our application state data: 

const [mapLongitude, setMapLongitude] = useState(-121.91599);
const [mapLatitude, setMapLatitude] = useState(37.36765);
const [mapZoom, setMapZoom] = useState(13);
const [map, setMap] = useState({});

If you’ve used setState in a class-based react component, this should appear familiar. Instead of passing a single object containing all of our component’s state to setState, we use the State Hook to initialize each individual bit of application state we’d like to persist. We create State Hooks with the useState function, which accepts an initial state value as its only parameter. It returns two values: a state variable we can use to access the state, and a setter function we can call to update. 

We can’t keep these values in regular JavaScript variables because React re-runs our App function every time the component re-renders — which happens quite frequently. Since we'd like our data to stick around, we wrap it in State Hooks.

Most of the values are self-explanatory. We use mapLongitude, mapLatitude, and mapZoom to control how the map displays. The final state variable — map — holds a reference to the TomTom map object we will create.

Next, we have functions that update our state variables and update the map:

const increaseZoom = () => {
  if (mapZoom < MAX_ZOOM) {
    setMapZoom(mapZoom + 1);
  }
};

const decreaseZoom = () => {
  if (mapZoom > 1) {
    setMapZoom(mapZoom - 1);
  }
};

const updateMap = () => {
  map.setCenter([parseFloat(mapLongitude), parseFloat(mapLatitude)]);
  map.setZoom(mapZoom);
};

The first two simply call setter methods to update our App component’s state, and the updateMap function calls two TomTom Maps SDK methods to update the map itself. We update the mapLatitude and mapLongitude state variables inline later in the JSX template because their updates only require simple one-liners, like so:

<Input
   type="text"
   name="longitude"
   value={mapLongitude}
   onChange={(e) => setMapLongitude(e.target.value)}
/>

After the update functions, you’ll notice another hook call: useEffect. Before we dive into that, let's revisit the mapElement ref because it's the reason we need to use an Effect Hook.

The mapElement ref we created above works hand-in-hand with a div in the App component's JSX on line 110:

<div ref={mapElement} className="mapDiv"></div>

The ref={mapElement} attribute tells React the mapElement variable should hold a reference to the actual DOM element representing this div. We need this reference because the TomTom Maps SDK can't render into a piece of React's virtual DOM. It needs to work with a real DOM object.

React will happily comply, but there’s a catch: it'll only assign the reference after it finishes mounting the component in the DOM, because the div element we want a reference to doesn't exist until React finishes mounting the component. Unfortunately, React runs the code in the App function before the component finishes mounting. This puts us in a bind, because it means we can’t initialize our TomTom map in the App body. We must wait until the component finishes mounting so mapElement contains a DOM element we can work with.

How do we solve this? If you’ve worked with old-style class-based React components, you know they make it easy by providing a componentDidMount lifecycle method you can override. This method — as its name implies — runs after the component mounts so it is the perfect place to initialize a map. Luckily for us, there are Hook-based equivalents for all the React component lifecycle methods. 

In this case, we use the Effect Hook. We can see it in action starting on line 47 of the App component:

useEffect(() => {
  let map = tt.map({
    key: "<API key goes here>",
    container: mapElement.current,
    center: [mapLongitude, mapLatitude],
    zoom: mapZoom
  });
  setMap(map);
  return () => map.remove();
}, []);

This Effect Hook is the key to making our TomTom map work seamlessly with React. It runs after the component mounts, calling the TomTom Maps Web SDK’s tt.map function to create our map.

Next, the Effect Hook calls the setMap function our State Hook created earlier to store the TomTom map object we just created. We want to store a copy of this in our component's state because we'll need it to change the map's latitude, longitude, or zoom level later. If we didn't store it in a state variable, we’d never be able to access it again after the Effect Hook finishes executing. Finally, our Effect Hook function returns another function. React runs this function when it unmounts the App component, and in this case, the function simply removes our TomTom map from the DOM.

Also note that we pass an empty array as a second parameter to useEffect. By default — if we don’t supply useEffect a second parameter — React re-runs the effect every time a component’s state changes. If we don’t want this to happen, we provide an array of state variables the effect should observe, and the effect only reruns if one of the observed variables changes. Passing an empty array tells React we only want the effect to run once when it initially mounts the component into the DOM.

A final point to keep in mind: since your TomTom map manipulates the DOM directly, React can’t update it automatically the way it updates components living in React’s virtual DOM. If you change the latitude, longitude, or zoom values in the sample application, React dutifully updates the component’s state. Any state-dependent React user interface (UI) elements update immediately. React doesn't control our map’s DOM elements, though, the TomTom map object does.

Consequently, the map only updates when we click the Update Map button. This calls the updateMap function, which in turn calls the TomTom map object created by the Effect Hook, and feeds our component’s current state values into the app via method calls. Remember that, although we’ve embedded our map in a React application, it’s not quite first-class. You can update it using the state stored in React components, but sometimes you need to do extra work to update the map.

To see this in action, try changing latitude, longitude, and zoom levels and then click 'Update Map. Let's see what happens if we decide to head up to the arctic and take a look at downtown Iqaluit:
 

Screen Shot 2021-04-13 at 11.15.24 AM

Our React application can easily update the TomTom map to show any location on Earth!

That covers the essentials. Now that you know how refs and effects work together, you know how to integrate a TomTom web map into a modern hook-based React web application! 

Next Steps

Although adding a map to your React app seems like a pain when you’ve never done it before, now that you know how easy it is using refs and effects, you can give your app users the visual, location-targeted experience they have come to expect from modern apps.

Check out one of our other React tutorials here: Building a Responsive Location Search Component with a React Search Box.

To explore all the ways TomTom helps add location-based functions to your React app, visit the Developer Portal and register to get started for free today.

First published: 
Tuesday, April 13, 2021 - 20:19
Last edited: 
Monday, April 19, 2021 - 21:13