Building a Tesla Supercharger Locator App with React: A Beginner's Journey

Introduction: As a beginner in React, I recently started a new project to develop a Tesla Supercharger Locator app. The aim was to utilize JavaScript, React, React Router, Bootstrap, and the Google Maps API to create a user-friendly web application that allows users to easily find and explore Tesla Supercharger stations across the USA. Despite encountering a few errors and misconceptions along the way, the project was successfully completed within a span of 4-5 days. In this blog post, I will share the general idea behind the project and provide an overview of its key features and functionality.

Google Maps Integration: The app begins by loading a Google Map on the screen, occupying the entire viewport. Leveraging the power of the Google Maps API, the map is centered on the user's current location. Additionally, all Tesla Supercharger stations are marked on the map. To achieve this, I utilized the National Renewable Energy Laboratory (NREL) API, which provided access to the locations of Tesla Supercharger stations. By clicking on a marker, users can view brief information about the city, address, and name of the station. Furthermore, the app displays the optimal driving distance and estimated time required to reach that particular station.

The Map component is responsible for rendering the Google Map and handling various map-related features.

Displaying the Map

The component utilizes the GoogleMap component from the @react-google-maps/api library to render the map on the screen. It sets the container's width and height to occupy the entire viewport. The map is centered on the user's current location and restricted within the bounds of the USA.

const containerStyle = {
  width: "100vw",
  height: "100vh",
};

const usaBounds = {
  north: 49.3457868,
  south: 24.396308,
  west: -125.000000,
  east: -66.934570,
};

return (
  <GoogleMap
    mapContainerStyle={containerStyle}
    center={currentLocation}
    zoom={13}
    options={{
      restriction: {
        latLngBounds: usaBounds,
        strictBounds: false,
      },
    }}
  >
  ...
  </GoogleMap>
);

Current Location and Supercharger Markers: The component utilizes the useState hook to manage the current location and the selected Supercharger station. Initially, the current location is set to null, and the selected station is also set to null. The useEffect hook is used to fetch the user's current location using the browser's geolocation API. If successful, the latitude and longitude coordinates are stored in the currentLocation state.

const [currentLocation, setCurrentLocation] = useState(null);

useEffect(() => {
  if (navigator.geolocation) {
    navigator.geolocation.getCurrentPosition(
      (position) => {
        const { latitude, longitude } = position.coords;
        setCurrentLocation({ lat: latitude, lng: longitude });
      },
      (error) => {
        console.error("Error getting current location:", error);
      }
    );
  } else {
    console.error("Geolocation is not supported by this browser.");
  }
}, []);

Handling Supercharger Selection: When a Supercharger marker is clicked, the handleSelectedStation function is triggered, which updates the selectedStation state with the clicked station's information. This allows the component to display an info window with detailed information about the selected Supercharger.

const [selectedStation, setSelectedStation] = useState(null);

function handleSelectedStation(station) {
  setSelectedStation(station);
}

...

{stations.map(station => (
  <Marker
    key={station.id}
    position={{ lat: station.latitude, lng: station.longitude }}
    title={station.station_name}
    onClick={() => handleSelectedStation(station)}
  />
))}

Info Window and Directions: If a Supercharger station is selected (selectedStation is not null), an info window is displayed using the InfoWindowF component from @react-google-maps/api. The info window shows information such as the city, station name, address, and phone number of the selected Supercharger. Additionally, if the user's current location and a Supercharger station are both available, the component calculates the optimal driving distance and duration using the Google Maps Directions API.

import { InfoWindowF } from "@react-google-maps/api";

...

function handleCloseInfoWindow() {
  setSelectedStation(null);
}

function handleDirectionsResponse(response, status) {
  if (status === "OK") {
    const leg = response.routes[0].legs[0];
    setDistance(leg.distance.text);
    setDuration(leg.duration.text);
  } else {
    console.error("Directions request failed:", status);
    setDistance(null);
    setDuration(null);
  }
}

...

{selectedStation && (
  <InfoWindowF
    position={{
      lat: selectedStation.latitude,
      lng: selectedStation.longitude,
    }}
    onCloseClick={handleCloseInfoWindow}
    visible={selectedStation !== null}
  >
    <div>
      <h3>City: {selectedStation.city}</h3>
      <p>Station Name: {selectedStation.station_name.split(" - Tesla Supercharger")}</p>
      <p>Address: {selectedStation.street_address}</p>
      <p>Phone: {selectedStation.station_phone}</p>
      {distance && duration && (
        <p>
          Distance: {distance} | Duration: {duration}
        </p>
      )}
      <button onClick={handleGetDirections}>Get Directions</button>
    </div>
  </InfoWindowF>
)}

Opening Directions in a New Tab: The handleGetDirections function is called when the user clicks the "Get Directions" button within the info window. It constructs a directions URL using the current location and the selected Supercharger's coordinates. The URL is opened in a new browser tab, allowing the user to view turn-by-turn directions from their current location to the selected Supercharger.

function handleGetDirections() {
  if (currentLocation && selectedStation) {
    const origin = `${currentLocation.lat},${currentLocation.lng}`;
    const destination = `${selectedStation.latitude},${selectedStation.longitude}`;
    const directionsUrl = `https://www.google.com/maps/dir/?api=1&origin=${origin}&destination=${destination}`;
    window.open(directionsUrl, "_blank");
  }
}

Station Cards and Search Functionality

The second page of the app features station cards, sorted by their proximity to the user's current location. Each card displays detailed information about the Tesla Supercharger station, including the aforementioned city, address, name, optimal driving distance, and estimated travel time. Moreover, the card provides additional data such as pricing and access hours. Users have the option to add their personal reviews about the station or browse existing reviews submitted by others. To enhance usability, a search feature is incorporated, enabling users to enter any address across the USA and view the nearest Tesla Supercharger stations relative to that address.

We will explore the functionalities of the CardDisplay component, which is a crucial part of a fuel station application. This component is responsible for rendering and displaying individual cards for each station, providing users with valuable information. We will delve into the code and discuss how it utilizes React, React Bootstrap, and the Google Maps API. Let's dive in!

Importing Dependencies: To begin, we import the necessary dependencies for our component. These include React, React Bootstrap, uuidv4 from the uuid package, and other essential components.

import React from "react";
import { v4 as uuidv4 } from "uuid";
import IndividualCard from "./IndividualCard";
import Col from "react-bootstrap/Col";
import Row from "react-bootstrap/Row";
import { useState } from "react";
import PlacesAutocomplete, {
  geocodeByAddress,
  getLatLng,
} from "react-places-autocomplete";
import Search from "./Search";

State Variables: The CardDisplay component uses state variables to manage its functionality. Let's take a closer look at each of them.

const [address, setAddress] = useState("");
const [lat, setLat] = useState(currentLocation.lat);
const [lng, setLng] = useState(currentLocation.lng);
  • address stores the user-selected address for fuel station search.

  • lat and lng hold the latitude and longitude coordinates of the user's location.

Calculating Distances: The calculateDistances function calculates the distances between the user's location and each fuel station using latitude and longitude coordinates. It utilizes the calculateDistance helper function.

const calculateDistances = () => {
  const yourLatitude = lat;
  const yourLongitude = lng;

  return stations
    .map((station) => {
      const stationLatitude = station.latitude;
      const stationLongitude = station.longitude;

      const distance = calculateDistance(
        yourLatitude,
        yourLongitude,
        stationLatitude,
        stationLongitude
      );

      return {
        ...station,
        distance: distance,
      };
    })
    .sort((a, b) => a.distance - b.distance)
    .slice(0, 32);
};
  • calculateDistances calculates the distances between the user's location and each station using the calculateDistance function.

  • The resulting distances are added to the station objects and then sorted in ascending order.

  • The function returns an array of the closest 32 stations.

Handling Address Selection and Change: The handleAddressSelect function is responsible for handling the user's address selection. It triggers geocoding to obtain the latitude and longitude coordinates of the selected address.

const handleAddressSelect = async (selectedAddress) => {
  setAddress(selectedAddress);

  try {
    const results = await geocodeByAddress(selectedAddress);
    const selectedLocation = await getLatLng(results[0]);
    const { lat, lng } = selectedLocation;
    console.log(`Latitude: ${lat}, Longitude: ${lng}`);
    setLat(lat);
    setLng(lng);
    const addressArr = selectedAddress.split(", ");
  } catch (error) {
    console.log("Error:", error);
  }
};

function handleAddressChange(selectedAddress) {
  if (address.length === 1) {
    setLat(currentLocation.lat);
    setLng(currentLocation.lng);
  }
  setAddress((prevAddr) => selectedAddress);
}
  • handleAddressSelect sets the selected address, triggers geocoding, and retrieves the latitude and longitude coordinates.

  • The obtained coordinates are then stored in the lat and lng state variables.

  • handleAddressChange is called when the user changes the address input field.

  • If the address length is 1 (initial state), the component sets the user's current location as the latitude and longitude.

Rendering Individual Cards: The CardDisplay component renders individual cards for each station using the closestStations array obtained from the calculateDistances function.

<Row
  xs={1}
  md={4}
  className="g-4"
  style={{
    marginLeft: "100px",
    marginRight: "100px",
    display: "flex",
  }}
>
  {closestStations.map((station) => (
    <Col key={uuidv4()}>
      <div
        style={{
          display: "flex",
          flexDirection: "column",
          height: "100%",
        }}
      >
        <IndividualCard
          key={station.id}
          station={station}
          stations={stations}
          setStations={setStations}
          stationLatitude={station.latitude}
          stationLongitude={station.longitude}
          lat={lat}
          lng={lng}
        />
      </div>
    </Col>
  ))}
</Row>
  • The closestStations array is mapped to generate an IndividualCard component for each station.

  • The component is provided with necessary props such as station, stations, setStations, stationLatitude, stationLongitude, lat, and lng.

The functionalities of the IndividualCard component: This component is responsible for rendering an individual card for each station, providing users with station details, reviews, and the ability to add reviews. We will delve into the code and discuss how it utilizes React, React Bootstrap, and the Google Maps API. Let's dive in!

Importing Dependencies: To begin, we import the necessary dependencies for our component. These include React, React Bootstrap, and components such as Card, Button, Form, Modal, Accordion, Badge, Alert, Col, and Image. We also import the GoogleMap and Marker components from the @react-google-maps/api package.

import React, { useState, useEffect } from "react";
import Card from "react-bootstrap/Card";
import Button from "react-bootstrap/Button";
import Form from "react-bootstrap/Form";
import Modal from "react-bootstrap/Modal";
import Accordion from "react-bootstrap/Accordion";
import Badge from "react-bootstrap/Badge";
import Alert from "react-bootstrap/Alert";
import Col from "react-bootstrap/Col";
import Image from "react-bootstrap/Image";
import { GoogleMap, Marker } from "@react-google-maps/api";

State Variables: The IndividualCard component uses state variables to manage its functionality. Let's take a closer look at each of them.

const [showModal, setShowModal] = useState(false);
const [comment, setComment] = useState("");
const [currentLocation, setCurrentLocation] = useState(null);
const [selectedStation, setSelectedStation] = useState(null);
const [distance, setDistance] = useState(null);
const [duration, setDuration] = useState(null);
  • showModal tracks whether the review modal is visible or hidden.

  • comment stores the value of the user's review comment.

  • currentLocation holds the latitude and longitude coordinates of the user's current location.

  • selectedStation represents the currently selected station.

  • distance stores the distance between the user's location and the selected station.

  • duration holds the estimated duration of the journey to the selected station.

Fetching User's Current Location: The useEffect hook is used to fetch the user's current location when the component mounts. It utilizes the navigator.geolocation API to obtain the latitude and longitude coordinates.

useEffect(() => {
  if (navigator.geolocation) {
    navigator.geolocation.getCurrentPosition(
      (position) => {
        const { latitude, longitude } = position.coords;
        setCurrentLocation({ lat: latitude, lng: longitude });
      },
      (error) => {
        console.error("Error getting current location:", error);
      }
    );
  } else {
    console.error("Geolocation is not supported by this browser.");
  }
}, []);
  • The navigator.geolocation.getCurrentPosition method is called to retrieve the user's current position.

  • If successful, the latitude and longitude coordinates are stored in the currentLocation state variable.

Calculating Directions and Displaying on the Map: The useEffect hook is also used to calculate directions and display them on the map when the selected station or current location changes. It utilizes the Google Maps API and the DirectionsService to obtain the route information.

useEffect(() => {
  if (selectedStation && currentLocation) {
    const directionsService = new window.google.maps.DirectionsService();
    const request = {
      origin: currentLocation,
      destination: {
        lat: stationLatitude,
        lng: stationLongitude,
      },
      travelMode: "DRIVING",
      drivingOptions: {
        departureTime: new Date(),
      },
    };
    directionsService.route(request, handleDirectionsResponse);
  }
}, [selectedStation, currentLocation]);
  • The DirectionsService is instantiated to request directions from the current location to the selected station.

  • The request object contains the origin, destination, travel mode, and departure time.

  • The handleDirectionsResponse function is used as the callback to process the directions response.

Handling Directions Response: The handleDirectionsResponse function is responsible for handling the response obtained from the Google Maps API. It extracts the distance and duration from the response and updates the respective state variables.

function handleDirectionsResponse(response, status) {
  if (status === "OK") {
    const leg = response.routes[0].legs[0];
    setDistance(leg.distance.text);
    setDuration(leg.duration.text);
  } else {
    console.error("Directions request failed:", status);
    setDistance(null);
    setDuration(null);
  }
}
  • If the status is "OK," the function extracts the distance and duration from the response and updates the state variables.

  • If the status is not "OK," an error message is logged, and the distance and duration state variables are set to null.

Handling Get Directions: The handleGetDirections function is called when the user clicks the "Get Directions" button. It constructs the URL with the origin and destination coordinates and opens it in a new browser tab.

function handleGetDirections(stationLatitude, stationLongitude) {
  if (currentLocation) {
    const origin = `${lat},${lng}`;
    const destination = `${stationLatitude},${stationLongitude}`;
    const directionsUrl = `https://www.google.com/maps/dir/?api=1&origin=${origin}&destination=${destination}`;
    window.open(directionsUrl, "_blank");
  }
}
  • The origin and destination variables are created by combining the latitude and longitude coordinates.

  • The directionsUrl is constructed with the origin and destination, and the window.open function opens it in a new tab.

Handling Review Input: The handleCommentChange function is called when the user types in the review input field. It updates the comment state variable with the input value.

function handleCommentChange(event) {
  setComment(event.target.value);
}

Adding a Review: The handleAddReview function is called when the user clicks the "Add Review" button. It sets the showModal state variable to true, displaying the review modal.

function handleAddReview() {
  setShowModal(true);
}

Closing the Review Modal: The handleCloseModal function is called when the user clicks the "Cancel" button or closes the review modal. It resets the comment state variable and sets the showModal state variable to false, hiding the review modal.

function handleCloseModal() {
  setComment("");
  setShowModal(false);
}

Adding a Comment/Review: The handleAddComment function is called when the user submits the review form. It sends a PATCH request to the server to update the comments for the selected station. After a successful update, the state variables are updated, and the review modal is closed.

function handleAddComment(e) {
  e.preventDefault();
  const updatedComments = station.comments
    ? [...station.comments, comment]
    : [comment];
  const updatedStation = {
    ...station,
    comments: updatedComments,
  };

  fetch(`${URL}/${station.id}`, {
    method: "PATCH",
    headers: {
      "Content-type": "application/json",
      Accept: "application/json",
    },
    body: JSON.stringify({ comments: updatedComments }),
  })
    .then((response) => response.json())
    .then((data) => {
      const updatedIndex = stations.findIndex((s) => s.id === station.id);
      const updatedStations = [
        ...stations.slice(0, updatedIndex),
        updatedStation,
        ...stations.slice(updatedIndex + 1),
      ];
      setStations(updatedStations);
    })
    .catch((error) => {
      console.log("Error:", error);
    });

  setShowModal(false);
  setComment("");
}
  • The function constructs an updatedComments array by adding the new comment to the existing comments array or creating a new array if no comments exist.

  • The updatedStation object is created by spreading the properties of the current station and updating the comments property with the updatedComments array.

  • A PATCH request is made to the server with the updated comments for the selected station.

  • Upon a successful response, the index of the updated station is found in the stations array, and a new updatedStations array is created with the updated station at the appropriate index.

  • The stations state variable is updated with the new updatedStations array, and the review modal is closed by setting showModal to false and resetting the comment state variable.

Google Maps Integration: The component uses the GoogleMap component from the @react-google-maps/api library to display a map with a marker at the selected station's location.

<GoogleMap
  mapContainerStyle={containerStyle}
  center={desitantionMarkers}
  zoom={15}
>
  {currentLocation && (
    <Marker
      position={desitantionMarkers}
      title="Station"
      onClick={(e) =>
        handleGetDirections(stationLatitude, stationLongitude)
      }
    />
  )}
</GoogleMap>
  • The GoogleMap component is rendered with the mapContainerStyle, center coordinates, and zoom level.

  • If the currentLocation is available, a Marker component is rendered at the station's coordinates.

  • The onClick event of the marker is set to call the handleGetDirections function to open directions to the station when clicked.

Review Display: The component displays the existing reviews/comments for the selected station using an Accordion component from the react-bootstrap library.

<Accordion defaultActiveKey="1">
  <Accordion.Item eventKey="0">
    <Accordion.Header>
      <span
        style={{
          fontSize: "16px",
          fontWeight: "bold",
          textAlign: "center",
          marginRight: "4px",
        }}
      >
        Reviews
      </span>
      {"\u00A0"}
      <Badge pill bg="secondary">
        {station.comments ? station.comments.length : 0}
      </Badge>
    </Accordion.Header>
    <Accordion.Body>
      {station.comments && station.comments.length > 0 ? (
        station.comments.map((comment, index) => (
          <Alert key={index} variant="dark">
            <Col xs={2} md={2}></Col>
            <Image src="./thumbnail.png" circle />
            {"\u00A0" + "\u00A0"}
            {comment}
          </Alert>
        ))
      ) : (
        <Alert variant="danger">No Reviews Yet</Alert>
      )}
      <div className="d-grid gap-2">
        <Button
          variant="dark"
          onClick={handleAddReview}
          direction="horizontal"
          justify="center"
        >
          Add Review
        </Button>
      </div>
    </Accordion.Body>
  </Accordion.Item>
</Accordion>
  • The Accordion component is rendered with the defaultActiveKey set to "1" to display the reviews expanded by default.

  • The Accordion.Header displays the "Reviews" title and a Badge component with the number of comments.

  • The Accordion.Body contains the individual comments/reviews displayed using the Alert component.

  • If there are no comments, a "No Reviews Yet" message is displayed.

  • The "Add Review" button is rendered with the Button component.

Implementing a Search Bar with Autocomplete in React

When building a location-based application, it's often essential to include a search feature that allows users to find specific places or addresses. In this blog post, we'll explore how to implement a search bar with autocomplete functionality in a React application using the react-places-autocomplete library. We'll also utilize the react-bootstrap library to style the form.

Prerequisites

Before getting started, make sure you have the following dependencies installed in your React project:

  • react

  • react-dom

  • react-places-autocomplete

  • react-bootstrap

You can install these dependencies using the following command:

npm install react react-dom react-places-autocomplete react-bootstrap

Getting Started

To begin, let's create a new functional component called Search to encapsulate the search bar functionality. Here's the initial code snippet:

import PlacesAutocomplete from 'react-places-autocomplete';
import Form from 'react-bootstrap/Form';

export default function Search({ handleAddressChange, handleAddressSelect, address }) {

  return (
    <>
      <div>
        <br></br>
        <Form.Group
          controlId="addressCityStateZip"
          style={{
            width: "60vh",
            position: "fixed",
            top: "16%",
            left: "29%",
            zIndex: "9999",
            borderRadius: "25px",
          }}
        >
          {/* Search Bar */}
        </Form.Group>
      </div>
    </>
  );
}

In this code snippet, we import the necessary dependencies and define the Search component. The component receives three props: handleAddressChange, handleAddressSelect, and address. The handleAddressChange and handleAddressSelect functions will be responsible for handling changes in the address input and selecting an address from the autocomplete suggestions, respectively. The address prop represents the current value of the address input.

To create the search bar with autocomplete functionality, we'll use the PlacesAutocomplete component provided by the react-places-autocomplete library. Let's add the necessary code to our component:

// ...

<Form.Group
  controlId="addressCityStateZip"
  style={{
    width: "60vh",
    position: "fixed",
    top: "16%",
    left: "29%",
    zIndex: "9999",
    borderRadius: "25px",
  }}
>
  <div>
    <PlacesAutocomplete
      value={address}
      onChange={handleAddressChange}
      onSelect={handleAddressSelect}
      searchOptions={{ types: ['address'], componentRestrictions: { country: 'us' } }}
    >
      {({ getInputProps, suggestions, getSuggestionItemProps, loading }) => (
        <div>
          <Form.Control
            {...getInputProps({
              placeholder: "Find a Charging Station Nearby...",
              size: "md",
            })}
            style={{ 
              position: "fixed", 
              width: "42%", 
              top: "8%", 
              zIndex: "9999", 
              backgroundColor: "rgba(211, 211, 211, 0.8)",
              borderRadius: "25px"}}
          />
          <div>
            {/* Autocomplete Suggestions */}
          </div>
        </div>
      )}
    </PlacesAutocomplete>
  </div>
</Form.Group>

// ...

Inside the Form.Group component, we wrap the PlacesAutocomplete component, which takes care of the autocomplete functionality. We pass several props to the PlacesAutocomplete component:

  • value: The current value of the address input, which is provided through the address prop.

  • onChange: A callback function that will be called whenever the address input changes. This function is responsible for updating the address value, and it is passed as the handleAddressChange prop.

  • onSelect: A callback function that will be called when the user selects an address from the autocomplete suggestions. This function is responsible for handling the selected address, and it is passed as the handleAddressSelect prop.

  • searchOptions: An object that configures the search options. In this case, we specify that the search should be restricted to addresses in the United States.

Next, we define a render prop function that receives several destructured parameters: getInputProps, suggestions, getSuggestionItemProps, and loading.

  • getInputProps is a function that provides props to the address input element. We spread its return value into the Form.Control component, including the placeholder and size props.

  • suggestions is an array of suggested addresses based on the user's input. We'll use this array to render the autocomplete suggestions.

  • getSuggestionItemProps is a function that provides props to each suggestion item. We'll use it to render the suggestions and handle user interactions.

  • loading is a boolean flag indicating whether the suggestions are still being loaded. We can use this flag to display a loading indicator if needed.

Rendering Autocomplete Suggestions

Now that we have the necessary components and props set up, let's render the autocomplete suggestions based on the user's input. Add the following code snippet inside the PlacesAutocomplete component:

// ...

<div>
  <Form.Control
    {...getInputProps({
      placeholder: "Find a Charging Station Nearby...",
      size: "md",
    })}
    style={{ 
      position: "fixed", 
      width: "42%", 
      top: "8%", 
      zIndex: "9999", 
      backgroundColor: "rgba(211, 211, 211, 0.8)",
      borderRadius: "25px"}}
  />
  <div>
    {loading && <div>Loading...</div>}
    {suggestions.map((suggestion) => {
      const style = {
        backgroundColor: suggestion.active ? "#e2e2e2" : "#fff",
      };
      return (
        <div
          {...getSuggestionItemProps(suggestion, { style })}
          key={suggestion.placeId}
        >
          {suggestion.description}
        </div>
      );
    })}
  </div>
</div>

// ...

In this code snippet, we render the address input field using the Form.Control component and apply some inline styles to position and style the input. The getInputProps function provides the necessary props to the Form.Control component.

Next, we render the autocomplete suggestions using the suggestions.map() method. For each suggestion, we create a div element and spread the props returned by getSuggestionItemProps to handle user interactions and style the suggestions based on the active flag.

The suggestion.description property contains the text of each suggestion, which is displayed to the user.

We also check the loading flag and display a "Loading..." message if the suggestions are still being fetched.

Putting It All Together

With the above code snippets combined, our Search component should look like this:

import PlacesAutocomplete from 'react-places-autocomplete';
import Form from 'react-bootstrap/Form';

export default function Search({ handleAddressChange, handleAddressSelect, address }) {

  console.log(address);
  return (
    <>
      <div>
        <br></br>
        <Form.Group
          controlId="addressCityStateZip"
          style={{
            width: "60vh",
            position: "fixed",
            top: "16%",
            left: "29%",
            zIndex: "9999",
            borderRadius: "25px",
          }}
        >
          <div>
            <PlacesAutocomplete
              value={address}
              onChange={handleAddressChange}
              onSelect={handleAddressSelect}
              searchOptions={{ types: ['address'], componentRestrictions: { country: 'us' } }}
            >
              {({ getInputProps, suggestions, getSuggestionItemProps, loading }) => (
                <div>
                  <Form.Control
                    {...getInputProps({
                      placeholder: "Find a Charging Station Nearby...",
                      size: "md",
                    })}
                    style={{ 
                      position: "fixed", 
                      width: "42%", 
                      top: "8%", 
                      zIndex: "9999", 
                      backgroundColor: "rgba(211, 211, 211, 0.8)",
                      borderRadius: "25px"}}
                  />
                  <div>
                    {loading && <div>Loading...</div>}
                    {suggestions.map((suggestion) => {
                      const style = {
                        backgroundColor: suggestion.active ? "#e2e2e2" : "#fff",
                      };
                      return (
                        <div
                          {...getSuggestionItemProps(suggestion, { style })}
                          key={suggestion.placeId}
                        >
                          {suggestion.description}
                        </div>
                      );
                    })}
                  </div>
                </div>
              )}
            </PlacesAutocomplete>
          </div>
        </Form.Group>
      </div>
    </>
  );
}

Adding New Stations

The third page of the app is dedicated to adding new stations. A simple form allows users to input all the necessary information about a Tesla Supercharger station, ensuring the app remains up-to-date with the latest station details.

Creating the Form Component

We'll begin by creating a new React component called NewStation. This component will render a form for adding a new charging station. Here's a simplified version of the component code:

import React, { useState } from 'react';
import { Form, Button } from 'react-bootstrap';
import PlacesAutocomplete from 'react-places-autocomplete';

const NewStation = () => {
  // State variables for form inputs
  const [stationName, setStationName] = useState('');
  const [address, setAddress] = useState('');
  const [city, setCity] = useState('');
  const [state, setState] = useState('');
  const [pricing, setPricing] = useState('');
  const [review, setReview] = useState('');
  const [chargerType, setChargerType] = useState('');
  const [accessDaysTime, setAccessDaysTime] = useState('');
  const [zip, setZip] = useState('');

  // Handle form submission
  const handleSubmit = (event) => {
    event.preventDefault();

    // Create a new station object
    const newStation = {
      stationName,
      address,
      city,
      state,
      pricing,
      review,
      chargerType,
      accessDaysTime,
      zip,
    };

    // Send a POST request to the API to add the station
    fetch('http://example.com/stations', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(newStation),
    })
      .then((response) => {
        if (response.ok) {
          alert('Station added successfully!');
          // Reset form inputs
          setStationName('');
          setAddress('');
          setCity('');
          setState('');
          setPricing('');
          setReview('');
          setChargerType('');
          setAccessDaysTime('');
          setZip('');
        } else {
          alert('Failed to add station.');
        }
      })
      .catch((error) => {
        console.log('Error:', error);
        alert('Failed to add station.');
      });
  };

  // JSX rendering
  return (
    <div>
      {/* Video background */}
      <video autoPlay loop muted style={{ position: 'fixed', width: '100%', height: 'auto', zIndex: '-1' }}>
        <source src="path/to/background-video.mp4" type="video/mp4" />
      </video>

      {/* Form overlay */}
      <div style={{ position: 'relative', backgroundColor: 'rgba(255, 255, 255, 0.9)', padding: '20px', borderRadius: '5px' }}>
        <h1>Add a Charging Station</h1>
        <Form onSubmit={handleSubmit}>
          {/* Station name */}
          <Form.Group controlId="stationName">
            <Form.Label>Station Name</Form.Label>
            <Form.Control type="text" placeholder="Enter station name" value={stationName} onChange={(e) => setStationName(e.target.value)} required />
          </Form.Group>

          {/* Address */}
          {/* ... Additional form fields ... */}

          {/* Submit button */}
          <Button variant="primary" type="submit">
            Add Station
          </Button>
        </Form>
      </div>
    </div>
  );
};

export default NewStation;

Form Inputs and Submitting the Data

The form component contains various form inputs for capturing the station details, such as the station name, address, pricing, review, and more. Each input is bound to a corresponding state variable using the useState hook.

When the form is submitted, the handleSubmit function is called. It creates a new station object with the form input values and sends a POST request to the API endpoint.

Conclusion

Throughout the development of this Tesla Supercharger Locator app, I gained valuable hands-on experience with JavaScript, React, React Router, Bootstrap, and the Google Maps API. Despite being a beginner, I successfully built an application that enables users to explore and locate Tesla Supercharger stations with ease. While I encountered a few challenges and made some mistakes along the way, the project served as an excellent learning opportunity. Moving forward, I will continue honing my skills in React and explore new possibilities in web development.