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
andlng
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 thecalculateDistance
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
andlng
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 anIndividualCard
component for each station.The component is provided with necessary props such as
station
,stations
,setStations
,stationLatitude
,stationLongitude
,lat
, andlng
.
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
anddestination
variables are created by combining the latitude and longitude coordinates.The
directionsUrl
is constructed with the origin and destination, and thewindow.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
tofalse
and resetting thecomment
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 themapContainerStyle
,center
coordinates, andzoom
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 thehandleGetDirections
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 aBadge
component with the number of comments.The
Accordion.Body
contains the individual comments/reviews displayed using theAlert
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.
Implementing the Search Bar
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 theaddress
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 thehandleAddressChange
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 thehandleAddressSelect
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 theForm.Control
component, including theplaceholder
andsize
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.