Back

Implementing Global Location Search for your Next.js App

Implementing Global Location Search for your Next.js App

In the dynamic landscape of travel applications, incorporating a versatile location search component has become instrumental in shaping user experiences and addressing the challenges faced by developers aiming for scalability and global applicability. With this article, you will be able to create a reusable location search component for your applications and implement and integrate geolocation solutions.

The intricacies of location diversity pose a significant challenge for developers in various domains, especially as it concerns the scalability of their applications. As one of many approaches to overcome this challenge, a global location Search component that can find various locations across the globe acts as a basic but indispensable tool to tackle this problem. This essential feature is not only a cornerstone for travel planning applications but also holds significance across various industries, including logistics and delivery applications.

The idea of this component is quite simple and can be stripped down to basically being a powerful input box capable of indexing as many locations on Earth as possible, from diverse urban landscapes to the more remote destinations. The geographical data returned by such a component is essential as it offers developers a solution that transcends geographical constraints.

With this geographical data obtained from this global location search component, developers can easily scale their applications to cater to users worldwide and enhance the customization of user experiences to include geographical preferences and considerations, all achieved with minimal code.

Common Use Cases for a Global Location Search Component

The global location search component is a very versatile, with use cases that cut across various industries, hence offering tailored solutions for diverse applications and significantly improving user experiences. These use cases include but are not limited to those discussed below.

  • Travel Applications: Implementing a global location search component is pivotal for travel applications. Geographical data obtained from the component can be put to good use by developers in implementing features for users like itinerary customization,travel-related services, rendering maps of the travel destination, locating tourist spots, restaurants, healthcare, and many other essential services in the vicinity of the destination location which may come in handy to the users.

  • Logistics and Delivery Applications: The location search component streamlines address input for logistics and delivery applications, ensuring accurate and efficient delivery routing. Delivery personnel can easily locate destinations accurately, optimizing delivery routes and enhancing overall logistics management.

  • E-commerce and Retail: In e-commerce and retail, the component facilitates location-based searches for products and services. Users can find nearby stores, check product availability, and receive personalized recommendations based on their geographical preferences, enhancing the shopping experience.

  • Social Networking: Global location search enhances social networking platforms by allowing users to discover and connect with others based on their location. It enables geotagging of posts, events, and activities, fostering local connections and community engagement.

  • Real Estate: In real estate applications, the component assists users in exploring properties based on geographical preferences. It provides information on local amenities, neighborhood insights, and property availability, aiding in the decision-making process for potential buyers or tenants.

Overview of Technologies Used

In a bid to implement a global location search component that is both user-friendly and highly functional, we would be taking advantage of the popular frameworks Next.js, the react-select library, and the Google Maps API. These technologies are discussed below, emphasizing why they were chosen for this task.

Next.js and Reactjs

These constitute the robust backbone of our project, offering a powerful combination for building interactive user interfaces with a focus on reusability. The benefits of using these frameworks for implementing the global location search component include:

  • Component-Based Reusability: React’s component-based architecture empowers us to create encapsulated UI components. This allows for easy component reuse throughout the application, fostering modularity and simplifying maintenance.

  • Declarative Syntax: React’s declarative syntax simplifies the expression of our application’s UI. React handles efficient updates and rendering of dynamic location options by defining how the UI should look based on the application’s state.

  • Server-Side Rendering (SSR): Next.js enhances React by introducing server-side rendering, improving performance by rendering pages on the server and ensuring seamless delivery of components to the client. The server-side of Next.js also gives us access to the API folder, which can be taken advantage of in implementing the global location search component

Note on Alternatives for Reactjs and Next.js Any of your preferred front-end frameworks that offer component-based reusability will also effectively implement your Global Location Search Component.

React-Select

This is a customizable select component that enhances the user experience by providing a flexible and customizable solution for creating select dropdowns. Some benefits which we will gain from using this package for our implementation include:

  • Async Data Loading: Usingreact-select, we can effortlessly manage asynchronous loading of data, making it ideal for dynamic location searches.
  • Customizable Styling: With react-Select, we can style our components to ensure a consistent and visually appealing user interface.
  • Built-In Accessibility: The component is designed with accessibility in mind, ensuring an inclusive experience for all users.

Note on Alternatives for React-Select While react-Select is a powerful choice, some alternatives offer similar functionalities for customizable dropdowns. Feel free to check out any of the below options: Downshift, React-Downshift, React-Selectize or any of your preferred flexible dropdown solutions

Google Maps API

At the very core of our component is the Google Maps API. The powerful and versatile geographical solution offered by Google enriches our application with its geolocation indexing and retrieval features. The Google Maps API comes with a load of different solutions for tackling various kinds of problems concerning geographical data. In our component, we will be making use of a few of these that come particularly from the Google Maps Places API. They include:

  • Geocoding API: This facilitates the conversion of addresses into geographic coordinates (Geocoding).

  • Autocomplete API: Enhanced user experience is achieved through Autocomplete functionality that enables predicting and suggesting locations as users type. This API also carries out the critical role of ensuring that locations inputted and selected by the user are indexable by the Google Maps GeoCoding API.

Note on Alternatives to Google Maps API: Although Google Maps API is a popular choice, alternatives like Mapbox and Leaflet also offer robust mapping solutions. The choice depends on specific project requirements, pricing considerations, and the desired level of customization. These alternatives provide flexibility for developers to tailor their mapping solutions to meet unique project needs.

In the upcoming sections, we will walk through the seamless integration of the chosen technologies, highlighting their flexibility and reusability in creating a global location search component for your travel app.

Demo Project Overview

From this section onwards, we will focus on developing an implementation of the global location search component using the aforementioned technologies - Next.js, React.js, react-Select, and the Google Maps API. Tailwind css will also be used for styling. This hands-on demonstration will guide you through the step-by-step process of integrating these technologies to create the location search component. In this demo, we will be creating a global search component for an imaginary travel application that allows a user to select his home location and destination location. Our location search component will provide autocomplete functionality to the user as well as return the address and geographical data in latitude and longitude, which may then be utilized for further development of the travel application.

Setting Up the Project

In this section, we will go through the process of setting up the tools to be used in our project. We will start by initializing a new Next.js project, installing the react-Select library for dynamic location searching, and obtaining a Google Maps API key to unlock powerful geolocation features.

Initializing a Next.js Project

Run the command below and choose your preferred Next.js configuration, a process this article assumes you are already familiar with. See https://nextjs.org/docs/getting-started/installation if you are not conversant with the process.

npx create-next-app@latest

Once this is done, run the command below

cd my-project-name

Replacing my-project-name with whatever name you gave to your next.js app folder during the installation. This will take you to the folder, where you can now continue the setup for this demo.

Installing React-Select

Run the following command to install react-Select as a dependency:

npm install react-select

Setting Up Google Maps API Key

To utilize the geolocation features of the Google Maps API, you’ll need an API key. To obtain this key, follow the steps below:

  1. Visit the Google Cloud Console
  2. Create a new project: Click on the project dropdown in the top bar and select “New Project.” Enter a name for your project and click “Create.”
  3. Enable the Maps JavaScript API:
    • In the sidebar, navigate to “APIs & Services” > “Dashboard.”
    • Click on ”+ ENABLE APIS AND SERVICES.”
    • Search for “Maps JavaScript API” and enable it.
    • Search for “Places API” and enable it.
  4. Create API Key:
    • In the sidebar, navigate to “APIs & Services” > “Credentials.”
    • Click on ”+ CREATE CREDENTIALS” and choose “API Key.”
    • Restrict the API key if needed (optional).
    • Copy the generated API key.
  5. Configure Next.js Environment Variable:
    • Create a .env.local file in the root of your project.
    • Add the following line to the file:
      NEXT_PUBLIC_GOOGLE_MAPS_KEY=YOUR_API_KEY
    • Replace YOUR_API_KEY with the API key you obtained.

With these steps, your Next.js project is set up, react-Select is integrated, and you have a valid Google Maps API key for geolocation features. You can start up your development server with

npm run dev

In the next sections, we’ll delve into the implementation of the Global Location Search component.

Creating the Search Component

We will begin by creating a JSX file in the components folder. We will call ours’ Location.jsx.

Implementing the UI with React-Select and Handling user input

"use client";
import React, {  useState } from "react";
import Select from "react-select";
// A list of all countries
import countryDb from "@/public/countryDb";

export default function Location() {
  const [selectedHomeCountryOption, setSelectedHomeCountryOption] =
    useState(null);
  const [selectedHomeCityOption, setSelectedHomeCityOption] = useState(null);
  const [selectedDestinationCityOption, setDestinationCityOption] =
    useState(null);
    const handleSubmit = async (e) => {
        e.preventDefault();  
      };
  return (
    <main className="pb-20 w-full text-sm">
      <section className="h-full px-4 md:px-5 lg:px-20 xl:px-28 w-full flex flex-col gap-10 pt-5 md:pt-10">
        <div>
          <h1 className="font-semibold text-lg md:text-2xl text-prim">
            Connect Better!
          </h1>
          <p className="text-sm text-sec">
            Help us link your profile with other like-minded people
          </p>
        </div>
        <div>
          <h2 className=" text-base md:text-xl text-prim font-medium">
            Travel Information
          </h2>
          <form className=" flex flex-wrap gap-x-12 gap-y-20 w-full mt-2 md:mt-4">
            <div className="w-full md:w-2/5">
              <label htmlFor="homeCountry">Current Country</label>
              <Select
                className="text-prim placeholder-black placeholder-opacity-25"
                defaultValue={selectedHomeCountryOption}
                onChange={setSelectedHomeCountryOption}
                options={countryDb}
                id="homeCountry"
                placeholder={"Select a Country"}
              />
            </div>
            <div className="w-full md:w-2/5">
              <label htmlFor="homeCity">Home City</label>
              <Select
                className="text-prim placeholder-black placeholder-opacity-25"
                defaultValue={selectedHomeCityOption}
                onChange={setSelectedHomeCityOption}
                options={[]}
                id="homeCity"
                placeholder={"Search for a City"}
              />
            </div>
            <div className="w-full md:w-2/5">
              <label htmlFor="destinationCity">Destination City</label>
              <Select
                className="text-prim placeholder-black placeholder-opacity-25"
                defaultValue={selectedDestinationCityOption}
                onChange={setDestinationCityOption}
                options={[]}
                id="destinationCity"
                placeholder={"Search for a City"}
              />
            </div>
            <button
              className="bg-tert w-full md:w-2/5 py-3  md:mt-4 rounded-3xl text-white opacity-100"
              onClick={(e) => {
                handleSubmit(e);
              }}
            >
              Proceed
            </button>
          </form>
        </div>
      </section>
    </main>
  );
}

Screenshot (56) This gives us the skeleton for our project. Google Maps API is yet to be integrated at this point. However, an external file is used to list out country options. The link to the external file is available here.

Implementing Global Search Functionality

Now that we have our visuals out of the way, we can begin implementing some functionality. We will be implementing two slightly different versions. The selection for the destination city will be very basic and straight to the point, while the selection for the home city will utilize some of the special parameters of the Google Map Places Autocomplete API, specifically the cities and country parameters. To know more about the parameters offered by this API visit https://developers.google.com/maps/documentation/places/web-service/autocomplete?.

In the code block below, we will integrate react-select and Google Maps API to enable retrieval of related locations based on user input.

"use client";
import React, { useEffect, useState } from "react";
import Select from "react-select";
//AsyncSelect component is used for loading dynamic options in react-select
import AsyncSelect from "react-select/async";
// A list of all countries
import countryDb from "@/public/countryDb";

export default function Location() {
  const [selectedHomeCountryOption, setSelectedHomeCountryOption] =
    useState(null);
  const [selectedHomeCityOption, setSelectedHomeCityOption] = useState(null);
  const [selectedDestinationCityOption, setDestinationCityOption] =
    useState(null);
  const [homeGeoLocation, setHomeGeoLocation] = useState(null);
  const [destinationGeoLocation, setDestinationGeoLocation] = useState(null);

  //this function loads in the options to react-select AsyncSelect component asynchronously
  //here we used the home country select to scope our query to the Google Maps Places API to a particular country, boosting accuracy
  const loadHomeCityOptions = async (inputValue, callback) => {
    if (selectedHomeCountryOption && inputValue) {
      try {
        const response = await fetch( `https://maps.googleapis.com/maps/api/place/autocomplete/json?input=${inputValue}&types=%28cities%29&components=country:${selectedHomeCountryOption.value}&key=${process.env.NEXT_PUBLIC_GOOGLE_MAPS_KEY}`
        );
        const data = await response.json();
        let places = [];
        data?.data?.predictions?.map((place, i) => {
          places = [
            ...places,
            { value: place.description, label: place.description },
          ];
        });
        callback(places);
      } catch (error) {
        console.error(error);
      }
    }
  };

  //this function loads in the options to react-select AsyncSelect component asynchronously
  //here we do not scope the country location, the Google Maps Places API will however give preference to your browser location
  const loadDestinationCityOptions = async (inputValue, callback) => {
    if (inputValue) {
      try {
        const response = await fetch(    `https://maps.googleapis.com/maps/api/place/autocomplete/json?input=${inputValue}&key=${process.env.NEXT_PUBLIC_GOOGLE_MAPS_KEY}`
        );
        const data = await response.json();
        let places = [];
        data?.data?.predictions?.map((place, i) => {
          places = [
            ...places,
            { value: place.description, label: place.description },
          ];
        });

        callback(places);
      } catch (error) {
        console.error(error);
      }
    }
  };
  const handleSubmit = async (e) => {
    e.preventDefault();
  };
  return (
    <main className="pb-20 w-full text-sm">
      <section className="h-full px-4 md:px-5 lg:px-20 xl:px-28 w-full flex flex-col gap-10 pt-5 md:pt-10">
        <div>
          <h1 className="font-semibold text-lg md:text-2xl text-prim">
            Connect Better!
          </h1>
          <p className="text-sm text-sec">
            Help us link your profile with other like-minded people
          </p>
        </div>
        <div>
          <h2 className=" text-base md:text-xl text-prim font-medium">
            Travel Information
          </h2>
          <form className=" flex flex-wrap gap-x-12 gap-y-6 w-full mt-2 md:mt-4">
            <div className="w-full md:w-2/5">
              <label htmlFor="homeCountry">Current Country</label>
              <Select
                className="text-prim placeholder-black placeholder-opacity-25"
                defaultValue={selectedHomeCountryOption}
                onChange={setSelectedHomeCountryOption}
                options={countryDb}
                id="homeCountry"
                placeholder={"Select a Country"}
              />
            </div>
            <div className="w-full md:w-2/5">
              <label htmlFor="homeCity">Home City</label>
              {/* note this change */}
              <AsyncSelect
                className="text-prim placeholder-black placeholder-opacity-25"
                defaultValue={selectedHomeCityOption}
                onChange={setSelectedHomeCityOption}
                loadOptions={loadHomeCityOptions}
                id="homeCity"
                placeholder={"Search for a City"}
              />
            </div>
            <div className="w-full md:w-2/5">
              <label htmlFor="destinationCity">Destination City</label>
              {/* note this change */}
              <AsyncSelect
                className="text-prim placeholder-black placeholder-opacity-25"
                defaultValue={selectedDestinationCityOption}
                onChange={setDestinationCityOption}
                loadOptions={loadDestinationCityOptions}
                id="destinationCity"
                placeholder={"Search for a City"}
              />
            </div>

            <button
              className="bg-tert w-full md:w-2/5 py-3  md:mt-4 rounded-3xl text-white opacity-100"
              onClick={(e) => {
                handleSubmit(e);
              }}
            >
              Proceed
            </button>
          </form>
        </div>
      </section>
    </main>
  );
}

Screenshot (57) The image above shows the functioning home city search; note how all the searches are scoped to the selected country Screenshot (58)

The image above shows the functioning destination city search. As you can see it is not scoped to any country.

Handling Selected Locations

Let’s see how to handle the found locations.

Retrieving and Displaying Geolocation Details

In this section, we will round up our implementation by using the Google Maps GeoCoding API to get the longitude and latitude of the addresses returned by the Google Maps Places AutoComplete API. The code below shows the full implementation of our demo

"use client";
import React, { useEffect, useState } from "react";
import Select from "react-select";
import AsyncSelect from "react-select/async";
// A list of all countries
import countryDb from "@/public/countryDb";

export default function Location() {

  const [selectedHomeCountryOption, setSelectedHomeCountryOption] =
    useState(null);
  const [selectedHomeCityOption, setSelectedHomeCityOption] = useState(null);
  const [selectedDestinationCityOption, setDestinationCityOption] =
    useState(null);
  const [homeGeoLocation, setHomeGeoLocation] = useState(null);
  const [destinationGeoLocation, setDestinationGeoLocation] = useState(null);

  const loadDestinationCityOptions = async (inputValue, callback) => {
    if (inputValue) {
      try {
        const response = await fetch( `https://maps.googleapis.com/maps/api/place/autocomplete/json?input=${inputValue}&key=${process.env.NEXT_PUBLIC_GOOGLE_MAPS_KEY}`
        );
        const data = await response.json();
        let places = [];
        data?.data?.predictions?.map((place, i) => {
          places = [
            ...places,
            { value: place.description, label: place.description },
          ];
        });

        callback(places);
      } catch (error) {
        console.error(error);
      }
    }
  };
  const loadHomeCityOptions = async (inputValue, callback) => {
    if (selectedHomeCountryOption && inputValue) {
      try {
        const response = await fetch(           `https://maps.googleapis.com/maps/api/place/autocomplete/json?input=${inputValue}&types=%28cities%29&components=country:${selectedHomeCountryOption.value}&key=${process.env.NEXT_PUBLIC_GOOGLE_MAPS_KEY}`
        );
        const data = await response.json();
        let places = [];
        data?.data?.predictions?.map((place, i) => {
          places = [
            ...places,
            { value: place.description, label: place.description },
          ];
        });
        callback(places);
      } catch (error) {
        console.error(error);
      }
    }
  };

  //Function to get geolocation in longitude latitude using Google Maps GeoCoding API
  const getGeoLocation = async (address) => {
    try {
      const response = await fetch(       `https://maps.googleapis.com/maps/api/geocode/json?address=${address}&key=${process.env.NEXT_PUBLIC_GOOGLE_MAPS_KEY}`
    );
      const data = await response.json();
      return data;
    } catch (error) {
      console.error(error);
    }
  };
  useEffect(() => {
    let isMounted = true;
    const fetchData = async () => {
      try {
        if (selectedDestinationCityOption && selectedHomeCityOption) {
          const destinationData = await getGeoLocation(
            selectedDestinationCityOption.value
          );
          const homeData = await getGeoLocation(selectedHomeCityOption.value);
          if (isMounted) {           setDestinationGeoLocation(destinationData);
            setHomeGeoLocation(homeData);
          }
        }
      } catch (error) {
        console.error(error);
      }
    };
    fetchData();
    return () => {
      isMounted = false;
    };
  }, [
    selectedHomeCountryOption,
    selectedHomeCityOption,
    selectedDestinationCityOption,
  ]);
  const handleSubmit = async (e) => {
    e.preventDefault();
    if (homeGeoLocation && destinationGeoLocation) {
      const locationData = {
        homeLocation: {
          type: "Point",
          coordinates: [
            homeGeoLocation.geoLocation.lng,
            homeGeoLocation.geoLocation.lat,
          ],
          address: selectedHomeCityOption.value,
          country: selectedHomeCountryOption.value,
        },
        destinationLocation: {
          type: "Point",
          coordinates: [
            destinationGeoLocation.geoLocation.lat,
            destinationGeoLocation.geoLocation.lng,
          ],
          address: selectedDestinationCityOption.value,
        },
      };
      console.log(locationData);
    }
  };
  return (
    <main className="pb-20 w-full text-sm">
      <section className="h-full px-4 md:px-5 lg:px-20 xl:px-28 w-full flex flex-col gap-10 pt-5 md:pt-10">
        <div>
          <h1 className="font-semibold text-lg md:text-2xl text-prim">
            Connect Better!
          </h1>
          <p className="text-sm text-sec">
            Help us link your profile with other like-minded people
          </p>
        </div>
        <div>
          <h2 className=" text-base md:text-xl text-prim font-medium">
            Travel Information
          </h2>
          <form className=" flex flex-wrap gap-x-12 gap-y-6 w-full mt-2 md:mt-4">
            <div className="w-full md:w-2/5">
              <label htmlFor="homeCountry">Current Country</label>
              <Select
                className="text-prim placeholder-black placeholder-opacity-25"
                defaultValue={selectedHomeCountryOption}
                onChange={setSelectedHomeCountryOption}
                options={countryDb}
                id="homeCountry"
                placeholder={"Select a Country"}
              />
            </div>
            <div className="w-full md:w-2/5">
              <label htmlFor="homeCity">Home City</label>
              <AsyncSelect
                className="text-prim placeholder-black placeholder-opacity-25"
                defaultValue={selectedHomeCityOption}
                onChange={setSelectedHomeCityOption}
                loadOptions={loadHomeCityOptions}
                id="homeCity"
                placeholder={"Search for a City"}
              />
            </div>

            <div className="w-full md:w-2/5">
              <label htmlFor="destinationCity">Destination City</label>
              <AsyncSelect
                className="text-prim placeholder-black placeholder-opacity-25"
                defaultValue={selectedDestinationCityOption}
                onChange={setDestinationCityOption}
                loadOptions={loadDestinationCityOptions}
                id="destinationCity"
                placeholder={"Search for a City"}
              />
            </div>
            <button
              className="bg-tert w-full md:w-2/5 py-3  md:mt-4 rounded-3xl text-white opacity-100"
              onClick={(e) => {
                handleSubmit(e);
              }}
            >
              Proceed
            </button>
          </form>
        </div>
      </section>
    </main>
  );
}

Screenshot (61)

Screenshot console

The above images show the geographical data we have been able to obtain from the user’s input on the browser console. Now, this data could easily be used elsewhere in our application for a limitless number of creative purposes.

Using Nextjs, we can easily define an API route on the server to handle the calls to Google Maps API in the background, thereby protecting our API key and facilitating faster load times. This should be considered as you try to implement your version of the Global Location Search Component.

Summary

The article centers on a demo project showcasing a travel app’s global location search component. The crucial role of such a powerful and reusable component is explored in the introductory sections, and the implementation of the component takes the spotlight for the rest of the article. Here is a recap of key steps in implementing the global location search component.

  1. Setting up Next.js, installing react-Select, and obtaining a Google Maps API key.
  2. Building a UI leveraging react-Select for select inputs,
  3. Integrating Google Maps APIs with the AsyncSelect component from react-select to create dynamic location searching and asynchronous loading.
  4. The integration of the Google Maps Geocoding API enabled location data retrieval in the form of longitude and latitude for selected addresses.

References

Understand every bug

Uncover frustrations, understand bugs and fix slowdowns like never before with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data. Check our GitHub repo and join the thousands of developers in our community.

OpenReplay