Adding Advanced TomTom API Features to a Modern React App

See how TomTom’s maps give developers a quick and easy-to-use option when adding interactive maps to a React app.

Adding Advanced TomTom API Features to a Modern React App

Digital maps like TomTom’s have become critical for navigation around cities and across continents. We now have access to real-time directions to guide us when moving from one location to other.

Plus, we can even get information about a place to enhance our user experience. For instance, we can search for a restaurant and get the address, website, and sometimes even pictures to better understand its atmosphere.

In a previous article, we explored how to add a TomTom map to a modern React app. In this article, we’ll discuss how to use two other TomTom APIs in a React app: the Search API and Routing API. We’ll build a component in React enabling users to search for a location and get a list of responses fulfilling the request.

51403380760_6910bc70b3_o.gif

Then, we’ll build another component to find the distance between two locations using their unique latitude and longitude. The response will contain information like the distance between them and the time to complete the journey.

You should be familiar with React and JavaScript to follow along. You can also find a demo on CodeSandbox. Adding TomTom maps to a React app is painless. Let’s get started.

51401654882_7e789c088a_o.gif

SETTING UP OUR TOOLS

Installing Node.js

To create a React app, we first need to ensure we have Node.js installed on our computer. If you haven’t built a React app before, you can check to see if you have Node.js installed by typing the following into your terminal:

node -v

If you don't have it, go to the Node.js website to download the latest version.

Creating a React App

Once that’s complete, we can get started with our React app by running this command:

npx create-react-app tomtom-maps

Then, we navigate into our project folder on the terminal:

cd tomtom-maps

Installing React-Toastify

Let’s also install react-toastify to show errors:

npm i react-toastify

Installing TomTom’s Tools

To use TomTom’s searching and routing services, we first need to install the TomTom Maps SDK. We use this command to install the library:

npm i @tomtom-international/web-sdk-services

Then we also install TomTom’s Maps SDK to view the map on the Document Object Model (DOM):

npm i @tomtom-international/web-sdk-maps

To use these services in your app, you first need to register as a TomTom developer. It’s free to sign up. Once you’re signed up, you can enjoy thousands of transactions daily — even for commercial applications. If your app’s popularity skyrockets later, you can always pay as you grow.

You’ll get an API Key with your account. Take note of the API key, because we’ll come back to it shortly.

USING THE TOMTOM SEARCH API

By default, TomTom’s Search API performs a fuzzy search when we make a query. If our queries contain the name of a specific place, like schools, hospitals, or restaurants, for example, it treats them as Points of Interest (POI).

The fuzzy search typically takes the query and returns all related results. These results enable us to locate addresses and POIs that partially match a specific query text.

To create our search component, we’ll use function components with React Hooks.

First, we import the library tt (see below). Also, we import useState to help us control our state within the component:

import tt from "@tomtom-international/web-sdk-services";
import { useState } from "react";

Then, we use useState to initialize our string’s state. It also handles the result object we get back after sending the query.

  const [name, setName] = useState("");
  const [result, setResult] = useState({});

Within our functional component, we next create a function to handle the search logic. We pass the argument name into fuzzySearch, where it acts as our query. We also use the API key as an input here. Then, we assign res.results from our query to our result state using setResult.

const fuzzySearch = (name) => {
    tt.services
      .fuzzySearch({
        key: "<Your API Key>",
        query: name
      })
      .go()
      .then((res) => {
        console.log(res);
        const amendRes = res.results;
        console.log(amendRes)
        setResult(amendRes)
        console.log(result)
      })
      .catch((err) => {
        console.log(err);
      });
  };

Next, we loop through the results and list them. Then, write a new function and use a ternary operator to check when the result object populates. When it’s populated, we loop through it using map(). If it’s empty, it displays No location.

const resultList = (result.length > 0) ?
     result.map((resultItem) => (
    <div className="col-xs-12 col-md-4 col" key={resultItem.id}>
      <div className="box">
        <ResultBox result={resultItem} />
      </div>
    </div>
  )): <h2>No locations</h2>

Our component looks like this:

import React from 'react'
const ResultBox = ({result}) => (
  <div className="result">
    <div className="result-name">Location Address: {result.address.freeformAddress}</div>
    <div className="result-name">Location state: {result.address.localName}</div>
    <div className="result-name">Location country: {result.address.country}</div>
    <div className="result-type">City: {result.address.municipalitySubdivision}</div>
    {result.poi ?
      <div className="result-name">Location name: {result.poi.name}</div>
        :
      <h4>Location has no specific name</h4>
    }
  </div>
)
export default ResultBox

Here we have the data that we'll display to the user. We also write another ternary operator to check if the result contains a POI object. If it does, it shows result.poi.name. If not, it displays “Location has no specific name.”

Additional information is available inside the result object that we can use if needed.

DISPLAYING OUR COMPONENT

Now that we’ve set up the Search API, we display our component. To do that, we return this:

<div className="App">
      <input
        className="input"
        type="text"
        placeholder="Search Location"
        value={name}
        onChange={(e) => {
          setName(e.target.value);
        }}
        onKeyPress={(e) => {
          if (e.key === "Enter") {
            fuzzySearch(name);
          }
        }}
        required
      />
      {resultList}
    </div>

Anything we add, our input tag syncs with name using setName(). When we press the Enter key after typing, it triggers the fuzzySearch function and passes name as an argument. The {resultList} function also displays in the component.

Here’s the full component code block:

import "../styles.css";
import tt from "@tomtom-international/web-sdk-services";
import { useState } from "react";
import ResultBox from "./resultBox";

export default function FuzzySearch() {
  const [name, setName] = useState("");
  const [result, setResult] = useState({});

  const fuzzySearch = (name) => {
    tt.services
      .fuzzySearch({
        key: "<Your API Key>",
        query: name
      })
      .go()
      .then((res) => {
        console.log(res);
        const amendRes = res.results;
        console.log(amendRes)
        setResult(amendRes)
        console.log(result)
      })
      .catch((err) => {
        console.log(err);
      });
  };

  const resultList = (result.length > 0) ?
     result.map((resultItem) => (
    <div className="col-xs-12 col-md-4 col" key={resultItem.id}>
      <div className="box">
        <ResultBox result={resultItem} />
      </div>
    </div>
  )): <h2>No locations</h2>

  return (
    <div className="App">
      <input
        className="input"
        type="text"
        placeholder="Search Location"
        value={name}
        onChange={(e) => {
          setName(e.target.value);
        }}
        onKeyPress={(e) => {
          if (e.key === "Enter") {
            fuzzySearch(name);
          }
        }}
        required
      />
      {resultList}
    </div>
  );
}

USING THE TOMTOM ROUTING API

By default, TomTom’s Search API performs a fuzzy search when we make a query. If our queries contain the name of a specific place, like schools, hospitals, or restaurants, for example, it treats them as Points of Interest (POI).

The fuzzy search typically takes the query and returns all related results. These results enable us to locate addresses and POIs that partially match a specific query text.

To create our search component, we’ll use function components with React Hooks.

First, we import the library tt (see below). Also, we import useState to help us control our state within the component:

import tt from "@tomtom-international/web-sdk-services";
import { useState } from "react";

Then, we use useState to initialize our string’s state. It also handles the result object we get back after sending the query.

  const [name, setName] = useState("");
  const [result, setResult] = useState({});

Within our functional component, we next create a function to handle the search logic. We pass the argument name into fuzzySearch, where it acts as our query. We also use the API key as an input here. Then, we assign res.results from our query to our result state using setResult.

 const fuzzySearch = (name) => {
    tt.services
      .fuzzySearch({
        key: "<Your API Key>",
        query: name
      })
      .go()
      .then((res) => {
        console.log(res);
        const amendRes = res.results;
        console.log(amendRes)
        setResult(amendRes)
        console.log(result)
      })
      .catch((err) => {
        console.log(err);
      });
  };

Next, we loop through the results and list them. Then, write a new function and use a ternary operator to check when the result object populates. When it’s populated, we loop through it using map(). If it’s empty, it displays No location.

const resultList = (result.length > 0) ?
     result.map((resultItem) => (
    <div className="col-xs-12 col-md-4 col" key={resultItem.id}>
      <div className="box">
        <ResultBox result={resultItem} />
      </div>
    </div>
  )): <h2>No locations</h2>

Our component looks like this:

import React from 'react'
const ResultBox = ({result}) => (
  <div className="result">
    <div className="result-name">Location Address: {result.address.freeformAddress}</div>
    <div className="result-name">Location state: {result.address.localName}</div>
    <div className="result-name">Location country: {result.address.country}</div>
    <div className="result-type">City: {result.address.municipalitySubdivision}</div>
    {result.poi ?
      <div className="result-name">Location name: {result.poi.name}</div>
        :
      <h4>Location has no specific name</h4>
    }
  </div>
)
export default ResultBox

Here we have the data that we'll display to the user. We also write another ternary operator to check if the result contains a POI object. If it does, it shows result.poi.name. If not, it displays “Location has no specific name.”

Additional information is available inside the result object that we can use if needed.

DISPLAYING OUR COMPONENT

Now that we’ve set up the Search API, we display our component. To do that, we return this:

<div className="App">
      <input
        className="input"
        type="text"
        placeholder="Search Location"
        value={name}
        onChange={(e) => {
          setName(e.target.value);
        }}
        onKeyPress={(e) => {
          if (e.key === "Enter") {
            fuzzySearch(name);
          }
        }}
        required
      />
      {resultList}
    </div>

Anything we add, our input tag syncs with name using setName(). When we press the Enter key after typing, it triggers the fuzzySearch function and passes name as an argument. The {resultList} function also displays in the component.

Here’s the full component code block:

import "../styles.css";
import tt from "@tomtom-international/web-sdk-services";
import { useState } from "react";
import ResultBox from "./resultBox";

export default function FuzzySearch() {
  const [name, setName] = useState("");
  const [result, setResult] = useState({});

  const fuzzySearch = (name) => {
    tt.services
      .fuzzySearch({
        key: "<Your API Key>",
        query: name
      })
      .go()
      .then((res) => {
        console.log(res);
        const amendRes = res.results;
        console.log(amendRes)
        setResult(amendRes)
        console.log(result)
      })
      .catch((err) => {
        console.log(err);
      });
  };

  const resultList = (result.length > 0) ?
     result.map((resultItem) => (
    <div className="col-xs-12 col-md-4 col" key={resultItem.id}>
      <div className="box">
        <ResultBox result={resultItem} />
      </div>
    </div>
  )): <h2>No locations</h2>

  return (
    <div className="App">
      <input
        className="input"
        type="text"
        placeholder="Search Location"
        value={name}
        onChange={(e) => {
          setName(e.target.value);
        }}
        onKeyPress={(e) => {
          if (e.key === "Enter") {
            fuzzySearch(name);
          }
        }}
        required
      />
      {resultList}
    </div>
  );
}

##USING THE TOMTOM ROUTING API

We next include TomTom’s Routing API. Similar to the Search API section, we import tomtom and useState. However, we also import react-toastify just in case we need to display an error.

import tt from "@tomtom-international/web-sdk-services";
import * as ttmaps from "@tomtom-international/web-sdk-maps"
import { useState, useRef, useEffect } from "react";
import { ToastContainer, toast } from "react-toastify";
import "react-toastify/dist/ReactToastify.css";

We also use useState to hold our component’s state, which includes:

  • Start location’s latitude
  • Start location’s longitude
  • Destination’s latitude
  • Destination’s longitude

Plus, we use useRef to add the map to the DOM:

const [startLatitude, setStartLatitude] = useState("");
const [startLongitude, setStartLongitude] = useState("");
const [destinationLatitude, setDestinationLatitude] = useState("");
const [destinationLongitude, setDestinationLongitude] = useState("");
const [result, setResult] = useState({});
const mapElement = useRef();
const [map, setMap] = useState({});
const [longitude, setLongitude] = useState(-121.91599);
const [latitude, setLatitude] = useState(37.36765);

After the component loads, we automatically add the map to the DOM with useEffect. We add it to the mapElement ref, and the map centers with the latitude and longitude we have declared.

useEffect(() => {
    let map = ttmaps.map({
      key: "74x6pNZKyjeAejlBl8V1O6BQLWKaALJC",
      container: mapElement.current,
      stylesVisibility: {
        trafficIncidents: true,
        trafficFlow: true
      },
      center: [longitude, latitude],
      zoom: 14
    });
    setMap(map);
  }, [])

Next, we write our calculateRoute function that holds the logic behind calculating the route between two different locations. The function uses our API key and the start location’s and destination’s latitude and longitude in the format below. We use template literals to represent everything in a string.

We then store the response inside the result state using setResult and automatically center the map on the startLatitude and startLongitude.

const calculateRoute = () => {
    tt.services
      .calculateRoute({
        key: "<Your API Key>",
        locations: `${startLatitude},${startLongitude}:${destinationLatitude},${destinationLongitude}`
      })
      .go()
      .then(function (routeData) {
        map.setCenter([parseFloat(startLatitude), parseFloat(startLongitude)]);
        console.log(routeData.toGeoJson());
        const data = routeData.toGeoJson();
        setResult(data);
      })
      .catch((err) => {
        console.log(err);
        notify();
      });
  };

Then, resultList checks whether the result populates. If it’s populated, we can calculate and display the distance and time. If not, the app displays Add location to get route details instead. If there’s an error, it triggers notify.

const resultList = result.features ? (
    <div className="col-xs-12 col-md-4 col" key={result.id}>
      <div className="box">
        <div className="result">
          <h4>
            Distance in KM : {result.features[0].properties.summary.lengthInMeters / 1000}
          </h4>
          <h4>
            Time Estimate for Journey is
            {` ${result.features[0].properties.summary.travelTimeInSeconds / 60} minutes`}
          </h4>
        </div>
      </div>
    </div>
  ) : (
    <h4>Add location to get route details</h4>
  );
  const notify = () => toast("Locations cannot be mapped. Check and map again");

Eventually, our user’s display includes the four input elements for the latitude and longitude pair, the button that triggers the calculateRoute, and the {resultList} to display the result. We add so the notification toaster can pop up when needed.

<div className="App">
      <ToastContainer />
      <div>
        <h3>Start Location</h3>
        <input
          className="input"
          type="text"
          placeholder="Latitude"
          value={startLatitude}
          onChange={(e) => {
            setStartLatitude(e.target.value);
          }}
          required
        />
        <input
          className="input"
          type="text"
          placeholder="Longitude"
          value={startLongitude}
          onChange={(e) => {
            setStartLongitude(e.target.value);
          }}
          required
        />
        <h3>Destination</h3>
        <input
          className="input"
          type="text"
          placeholder="Latitude"
          value={destinationLatitude}
          onChange={(e) => {
            setDestinationLatitude(e.target.value);
          }}
          required
        />
        <input
          className="input"
          type="text"
          placeholder="Longitude"
          value={destinationLongitude}
          onChange={(e) => {
            setDestinationLongitude(e.target.value);
          }}
          required
        />
      </div>
      <button
        onClick={(e) => {
          calculateRoute();
        }}
      >
        Calculate routeData
      </button>
      {resultList}
      <div className="map" ref={mapElement}></div>
    </div>

Finally, we go into App.js and use react-router-dom to handle navigation inside this project. We import all the components and use the router library to create routes.

import "./styles.css";
import FuzzySearch from "./components/FuzzySearch";
import { BrowserRouter as Router, Switch, Route, Link } from "react-router-dom";
import CalculateRoute from "./components/CalculateRoute"
export default function App() {
  return (
    <Router>
      <h3><Link to="/">Search Location/Address</Link></h3>
      <h3><Link to="/calculate">Calculate Route Distance</Link></h3>
    <Switch>
      <Route path='/' exact component={FuzzySearch} />
      <Route path='/calculate' component={CalculateRoute} />
    </Switch>
  </Router>
  );
}

We now have a simple mapping app for a user to search for a location. The app displays related results, including Points of Interest. Our other app component calculates a route between the user’s start location and destination, helping them get to where they need to go.

NEXT STEPS

We have explored how to use TomTom’s Search API to find addresses and locations. We also covered using the Route API to calculate road distance and travel time. Plus, we integrated it all into a React application.

You can further challenge yourself to explore TomTom maps in your React app by connecting the two components we’ve built. Search for locations, select two separate places (each location Search API returns has a latitude and longitude), and find the distance between them directly within the app.

Now that you see how easy it is to place interactive TomTom maps within your React app and some of the features that will enhance your users’ experience, you can add maps to your next great app for free. Sign up to learn more and start using TomTom Maps!

This article was originally published at developer.tomtom.com/blog.