Building a Fullstack Road trip mapper app using the absolute power of MERN stack 🔥
This article concentrates on the most critical tasks and concepts for better understanding and building MERN stack applications from the ground up. It's for folks who are serious about learning about the MERN stack and want to concentrate on the essentials. We'll build a full-stack road trip mapper application where users can pin and map locations and view the sites pinned by other users, all using the MERN stack and leveraging the power of the Mapbox API. This blog session will teach you the fundamentals of MERN stack technology as well as advanced concepts and operations.
Here's a quick preview of our application's final version:
There is a separate article where you may learn about the MERN stack in very great detail.
Setting up the folder structure
Create two folders inside your project directory called client and server, then open them in Visual Studio Code or any other code editor of your choice.
Now, we'll create a MongoDB database, a Node and Express server, a database schema to represent our project case study application, and API routes to create, read, update, and delete data and information from the database using npm and the appropriate packages. So, open a command prompt, navigate to your server's directory, and then run the code below.
npm init -yes
Configuring package.json file
Execute the following commands in the terminal to install the dependencies.
npm install cors dotenv express express-rate-limit mongoose nodemon body-parser helmet morgan rate-limit-mongo
Dotenv: Dotenv is a zero-dependency module that loads environment variables from a .env file into process.env
cors: This module allows to relax the security applied to an API
express: Fast, unopinionated, minimalist web framework for node.
express-rate-limit: Basic IP rate-limiting middleware for Express. It is used to limit repeated requests to public APIs and/or endpoints such as password reset.
mongoose: It is an Object Data Modeling library for MongoDB and Node. js
nodemon: This module helps to develop node.js based applications by automatically restarting the application when file changes in the directory are detected.
body-parser: Node.js body parsing middleware.
helmet: Helmet.js fills in the gap between Node.js and Express.js by securing HTTP headers that are returned by Express applications.
morgan : HTTP request logger middleware for node.js
rate-limit-mongo : MongoDB store for the express-rate-limit middleware.
The "package.json" file should look like this after the dependencies have been installed.
And also, remember to update the scripts as well.
Now go to your server directory, create a src folder, and an index.js file there.
Setting up index.js
Import express module.
Import and configure dotenv module
Import helmet module.
Import morgan module.
Import CORS module
Use express() to initialize our app.
//src/index.js
const express = require('express');
// NOTE morgan is a logger
const morgan = require('morgan');
const helmet = require('helmet');
const cors = require('cors');
const mongoose = require('mongoose');
require('dotenv').config();
// app config
const app = express();
We may now utilize all of the other methods on that app instance. Let's start with the fundamentals and very basic setups. Don't forget to set up the port and cors, too.
const express = require('express');
// NOTE morgan is a logger
const morgan = require('morgan');
const helmet = require('helmet');
const cors = require('cors');
const mongoose = require('mongoose');
require('dotenv').config();
const app = express();
const port = process.env.PORT || 4000;
app.use(morgan('common'));
app.use(helmet());
app.use(cors({
origin: process.env.CORS_ORIGIN,
}));
app.use(express.json());
app.get('/', (req, res) => {
res.json({
message: 'Hello There',
});
});
Now it's time to connect our server application to a real database. Here we'll use the MongoDB database, specifically the MongoDB cloud Atlas version, which means our database will be hosted on their cloud.
Setting up MongoDB Atlas cloud cluster
MongoDB is a document-oriented database that is open-source and cross-platform. MongoDB is a NoSQL database that stores data in JSON-like documents and has optional schemas. All versions were given under the AGPL license prior to October 16, 2018. All versions released after October 16, 2018, including bug fixes for prior versions, are covered by the SSPL license v1. You can also learn more about MongoDB setup and configuration from the following article.
To set up and start your MongoDB cluster, follow the exact same steps mentioned below.
Selecting a cloud service Provider
Database Deployment to the Cloud
Navigate to the network access tab and select "Add IP address."
Now, select the Choose a connection method.
Create a new variable called DATABASE_CONNECTION inside index.js . Create a string and paste the copied mongo DB connection URL into it. Now, inside it, type your username and password, removing any brackets and entering your own credentials. We'll create environmental variables to safeguard the credential later, but for now, let's add it this way. The second thing we'll need is a PORT, so just type in 4000 for now. Finally, we'll use mongoose to connect to our database, so type in mongoose. connect(), which is a function with two parameters. The DATABASE_CONNECTION will be the first, and the object with two choices will be the second. The first is useNewUrlParser, which we'll enable, and the second is useUnifiedTopology, which we'll enable as well. These objects are optional, but we will see some errors or warnings on our console. Let's chain it with .then() and .catch() inside,then() function. This will simply call the app and invoke listen, leading to two parameters: PORT and the callback function that will be executed if our application is successfully connected to the database. Finally, if the connection to the database is unsuccessful, we will simply console log our error message. Your index.js file should now look something like this.
//src/index.js
const express = require('express');
// NOTE morgan is a logger
const morgan = require('morgan');
const helmet = require('helmet');
const cors = require('cors');
const mongoose = require('mongoose');
require('dotenv').config();
const app = express();
const DATABASE_CONNECTION = process.env.DATABASE_URL;
mongoose.connect(DATABASE_CONNECTION, {
useNewUrlParser: true,
newUnifiedTopology: true,
});
app.use(morgan('common'));
app.use(helmet());
app.use(cors({
origin: process.env.CORS_ORIGIN,
}));
app.use(express.json());
app.get('/', (req, res) => {
res.json({
message: 'Hello There',
});
});
const port = process.env.PORT || 4000;
app.listen(port, () => {
console.log(`Currently Listening at http://localhost:${port}`);
});
Insert mongodb+srv into the .env file.
PORT=4000
DATABASE_URL=mongodb+srv://pramit:<password>@cluster0.8tw83.mongodb.net/myFirstDatabase?retryWrites=true&w=majority
CORS_ORIGIN=http://localhost:3000
We now have successfully connected our server to the database, let's create middleware first before we get started on building our backend application's routes and database schema. To do so, we'll need to create a new file called middlewares.js and within that file, we will create a two function called notFound and errorHandler
and export those functions. So let create notFound middleware so typically this middleware should be the last middleware that is registered so this middleware takes in req, res, and next. Basically, if a request ever made it here, it means we didn't locate the route users were searching for, so we'll create a variable and send them a message, and then we'll pass that to our next middleware, which is errorHander Middleware but before that don't forget to pass the response status of 404 as well. Now let's make our errorHandler middleware, which has four parameters instead of three, so we'll have (error,req, res, next). The first thing we'll do is set a status code and check whether it's 200 or use the status code that's already been specified, and then we'll simply set the status code, and then we'll respond with some JSON that will display the error message.
//middlewares.js
const notFound = (req, res, next) => {
const error = new Error(`Not Found - ${req.originalUrl}`);
res.status(404);
next(error);
};
const errorHandler = (error, req, res, next) => {
const statusCode = res.statusCode === 200 ? 500 : res.statusCode;
res.status(statusCode);
res.json({
message: error.message,
stack: process.env.NODE_ENV === "production" ? "nope" : error.stack,
});
};
module.exports = {
notFound,
errorHandler,
};
So, after modifying the middlewares.js file, import and use the middleware as needed in the index.js file.
//src/index.js
const express = require("express");
// NOTE morgan is a logger
const morgan = require("morgan");
const helmet = require("helmet");
const cors = require("cors");
const mongoose = require("mongoose");
require("dotenv").config();
const middlewares = require("./middlewares");
const app = express();
const DATABASE_CONNECTION = process.env.DATABASE_URL;
mongoose.connect(DATABASE_CONNECTION, {
useNewUrlParser: true,
newUnifiedTopology: true,
});
app.use(morgan("common"));
app.use(helmet());
app.use(
cors({
origin: process.env.CORS_ORIGIN,
})
);
app.use(express.json());
app.get("/", (req, res) => {
res.json({
message: "Hello There",
});
});
app.use(middlewares.notFound);
app.use(middlewares.errorHandler);
const port = process.env.PORT || 4000;
app.listen(port, () => {
console.log(`Currently Listening at http://localhost:${port}`);
});
let's create a LogEntry model. Create a folder called models and inside it, create one file called LogEntry.model.js and within that following file structure your DB schema by defining title, description, comments, image, ratings, latitude and longitude as shown below.
//models/LogEntry.model.js
const mongoose = require("mongoose");
const { Schema } = mongoose;
const requiredNumber = {
type: Number,
required: true,
};
const logEntrySchema = new Schema(
{
title: {
type: String,
required: true,
},
description: String,
comments: String,
image: String,
rating: {
type: Number,
min: 0,
max: 10,
default: 0,
},
latitude: {
...requiredNumber,
min: -90,
max: 90,
},
longitude: {
...requiredNumber,
min: -180,
max: 180,
},
visitDate: {
required: true,
type: Date,
},
},
{
timestamps: true,
}
);
const LogEntry = mongoose.model("collections", logEntrySchema);
module.exports = LogEntry;
The structure of your files and folders should now look like this.
Now that we've successfully created our DB Schema, let's get started on creating our routes for our backend application. To do so, we'll need to create a new folder inside the src directory and name it as routes Within the routes folder, we will create a js file called logs.routes.js.so first we must import express from "express" and also configure our router and import our recently created DB schema. Now we can begin adding our routes to it.
const { Router } = require("express");
const LogEntry = require("../models/LogEntry.model.js");
const { API_KEY } = process.env;
const router = Router();
fetches all the pinned location information.
router.get("/", async (req, res, next) => {
try {
const entries = await LogEntry.find();
res.json(entries);
} catch (error) {
next(error);
}
});
Insert/add a pinned location with authorized access
router.post("/", async (req, res, next) => {
try {
if (req.get("X-API-KEY") !== API_KEY) {
res.status(401);
throw new Error("Unauthorized Access");
}
const logEntry = new LogEntry(req.body);
const createdEntry = await logEntry.save();
res.json(createdEntry);
} catch (error) {
if (error.name === "ValidationError") {
res.status(422);
}
next(error);
}
});
exporting the router
module.exports = router;
Your logs.routes.js should resemble something like this
//src/routes/logs.routes.js
const { Router } = require("express");
const LogEntry = require("../models/LogEntry.model.js");
const { API_KEY } = process.env;
const router = Router();
router.get("/", async (req, res, next) => {
try {
const entries = await LogEntry.find();
res.json(entries);
} catch (error) {
next(error);
}
});
router.post("/", async (req, res, next) => {
try {
if (req.get("X-API-KEY") !== API_KEY) {
res.status(401);
throw new Error("Unauthorized Access");
}
const logEntry = new LogEntry(req.body);
const createdEntry = await logEntry.save();
res.json(createdEntry);
} catch (error) {
if (error.name === "ValidationError") {
res.status(422);
}
next(error);
}
});
module.exports = router;
Now, update your .env file
NODE_ENV=production
PORT=4000
DATABASE_URL=mongodb+srv://pramit:<password>@cluster0.8tw83.mongodb.net/myFirstDatabase?retryWrites=true&w=majority
CORS_ORIGIN=http://localhost:3000
API_KEY=roadtripmapper
Let's get started by importing the logs routes into your index.js file. We can now connect map pinned log info to our application using express middleware. Finally, your root index.js file should be like the following.
//src/index.js
const express = require("express");
// NOTE morgan is a logger
const morgan = require("morgan");
const helmet = require("helmet");
const cors = require("cors");
const mongoose = require("mongoose");
require("dotenv").config();
const middlewares = require("./middlewares");
const logs = require("./routes/logs.routes.js");
const app = express();
const DATABASE_CONNECTION = process.env.DATABASE_URL;
mongoose.connect(DATABASE_CONNECTION, {
useNewUrlParser: true,
newUnifiedTopology: true,
});
app.use(morgan("common"));
app.use(helmet());
app.use(
cors({
origin: process.env.CORS_ORIGIN,
})
);
app.use(express.json());
app.get("/", (req, res) => {
res.json({
message: "Hello There",
});
});
app.use("/api/logs", logs);
app.use(middlewares.notFound);
app.use(middlewares.errorHandler);
const port = process.env.PORT || 4000;
app.listen(port, () => {
console.log(`Currently Listening at http://localhost:${port}`);
});
After restarting the server, you should see something like this:
Setting up the frontend with react
In the next step let's start with the frontend and build it with react. The first thing you need to do is install Node.js if it isn't already installed on your machine. So, go to the Node.js official website and download the most recent version. You'll require Node js to utilize the node package manager, generally known as NPM. Now navigate to the client folder in your favorite code editor. Visual Studio Code will be my tool of choice. Then, in the integrated terminal, type npx create-react-app. This command will create a client application in the current directory with the name client.
There is a separate article where you may learn everything there is to know about
cleaning up boilerplate react project.
It's time to install some packages within react-boilerplate now that you've installed and cleaned it. so copy and paste the following command into your terminal.
npm i react-hook-form react-map-gl react-rating-stars-component react-responsive-animate-navbar
react-hook-form: Performant, flexible, and extensible forms library for React Hooks.
react-map-gl: react-map-gl is a suite of React components designed to provide a React API for Mapbox GL JS-compatible libraries
react-rating-stars-component: Simple star rating component for your React projects.
react-responsive-animate-navbar : simple, flexible & completely customizable responsive navigation bar component.
After installing all these packages your packge.json file of the client should look like this:
Let's construct two separate folders /components inside the components folder after we've installed all of our project's dependencies and name it as RoadTripNav and TripEntryForm .
Your file and folder structure should look something like this once you've added all of your components.
Now that you have all of the project's components set up, it's time to start coding. First, import the ReactNavbar from "react-responsive-animate-navbar" and customize the color of your navbar, add the logo to the public folder and import it directly, and don’t forget to add some social links as well. The following is an example of how the code should appear.
// components/RoadTripNav
import React from "react";
import * as ReactNavbar from "react-responsive-animate-navbar";
// import roadTripSvg from "../../assets/roadtrip.svg";
const RoadTripNav = () => {
return (
<ReactNavbar.ReactNavbar
color="rgb(25, 25, 25)"
logo="./logo.svg"
menu={[]}
social={[
{
name: "Twitter",
url: "https://twitter.com/pramit_armpit",
icon: ["fab", "twitter"],
},
]}
/>
);
};
export default RoadTripNav;
Before we go any further, let's set up our Mapbox. First, go to the Mapbox site and log in or sign up if you don't already have an account. Next, create your own custom map style in the Mapbox Studio and publish it. Finally, go back to the dashboard and copy the default public API key provided by MapBox.
Login or create your MapBox account
Click on design a custom map style
Customize your own style of the map inside the Mapbox studio
After you've successfully obtained your public token, go to the env file or create one if you don’t have and after that create a variable named as REACT_APP_MAPBOX_TOKEN, then paste that token into that variable. This is what your env file should look like.
REACT_APP_MAPBOX_TOKEN= ************************************ // add token
Before we go any further, let's make an api and styles folder in our root source directory. Inside the api folder, make a API.js file, and inside the styles folder, make a index.css file where all our styles of the application will be added. This is how your folder structure should appear.
Now go to the newly created API file and construct two functions called "listLogEntries" to collect all the log entries from the backend and "createLogEntries" to create or send the post request / post the entries to the backend, as well as export these functions. Also, don't forget to include the URL where your server is running.
//api/API.js
const API_URL = "http://localhost:4000";
// const API_URL = window.location.hostname === "localhost" ? "http://localhost:4000" : "https://road-trip-map-mern.herokuapp.com" ;
export async function listLogEntries() {
const response = await fetch(`${API_URL}/api/logs`);
// const json = await response.json();
return response.json();
}
export async function createLogEntries(entry) {
const api_key = entry.api_key;
delete entry.api_key;
const response = await fetch(`${API_URL}/api/logs`, {
method: "POST",
headers: {
"content-type": "application/json",
"X-API-KEY": api_key,
},
body: JSON.stringify(entry),
});
// const json = await response.json();
// return response.json();
let json;
if (response.headers.get("content-type").includes("text/html")) {
const message = await response.text();
json = {
message,
};
} else {
json = await response.json();
}
if (response.ok) {
return json;
}
const error = new Error(json.message);
error.response = json;
throw error;
}
Let's make a submission form for the pinned map location. To do so, open the TripEntryForm component from the component folder we previously made, import the useForm hook from react-hook-form, import createLogentries from api, and then import the useState hook from the React library because this hook will enable us to integrate the state into our functional component. useState(), unlike state in class components, does not work with object values. If necessary, we can use primitives directly and create multiple react hooks for multiple variables. Now, create two states: loading and error, and then destructure register and handleSubmit from the useForm() hook from "react-hook-form" library.After you've completed that, it's time to craft our form, but first let's create a function to handle our submit request. To do so, create an asynchronous onSubmit function and inside it, simply create a try-catch block. Inside the try block set the loading to true, configure the latitude and longitude, console log the data, and invoke the onClose function, and finally inside the catch block, pass the error message to the error state, set the loading to false and simply console log the error message and then simply create a form inside the return statement exactly shown in the code below.
// components/TripEntryForm.js
import React, { useState } from "react";
import { useForm } from "react-hook-form";
import { createLogEntries } from "../../api/API";
import "./TripEntryForm.css";
const TripEntryForm = ({ location, onClose }) => {
const [loading, setLoading] = useState(false);
const [error, setError] = useState("");
const { register, handleSubmit } = useForm();
const onSubmit = async (data) => {
try {
setLoading(true);
data.latitude = location.latitude;
data.longitude = location.longitude;
const created = await createLogEntries(data);
console.log(created);
onClose();
} catch (error) {
setError(error.message);
console.error(error);
setLoading(false);
}
};
return (
<form onSubmit={handleSubmit(onSubmit)} className="trip-form">
{error ? <h3 className="error-message">{error}</h3> : null}
<label htmlFor="api_key">Enter Password</label>
<input
type="password"
name="api_key"
placeholder="For demo, password => {roadtripmap} "
required
ref={register}
/>
<label htmlFor="title">Title</label>
<input name="title" placeholder="Title" required ref={register} />
<label htmlFor="comments">Comments</label>
<textarea
name="comments"
placeholder="Comments"
rows={3}
ref={register}
></textarea>
<label htmlFor="description">Description</label>
<textarea
name="description"
placeholder="Describe your journey"
rows={4}
ref={register}
></textarea>
<label htmlFor="image">Image</label>
<input name="image" placeholder="Image URL" ref={register} />
<label htmlFor="rating">Rating (1 - 10)</label>
<input name="rating" type="number" min="0" max="10" ref={register} />
<label htmlFor="visitDate">Visit Date</label>
<input name="visitDate" type="date" required ref={register} />
<button disabled={loading}>
<span>{loading ? "Submitting..." : "Submit your Trip"}</span>
</button>
</form>
);
};
export default TripEntryForm;
Also, don't forget to add the TripEntryForm styles inside that very own component folder and name it as TripEntryForm.css and paste the exact CSS code as mentioned below
//TripEntryForm.css
@import url("https://fonts.googleapis.com/css2?family=Fredoka+One&family=Poppins:ital,wght@0,200;0,400;1,200;1,300&family=Roboto:ital,wght@0,300;0,400;0,500;1,300;1,400;1,500&display=swap");
.trip-form label {
margin: 0.5rem 0;
display: block;
width: 100%;
color: rgb(255, 255, 255);
font-family: "Fredoka One", cursive;
}
.trip-form input {
margin: 0.5rem 0;
background-color: #2c2e41;
border-radius: 5px;
border: 0;
box-sizing: border-box;
color: rgb(255, 255, 255);
font-size: 12px;
height: 100%;
outline: 0;
padding: 10px 5px 10px 5px;
width: 100%;
font-family: "Fredoka One", cursive;
}
.trip-form textarea {
margin: 0.5rem 0;
background-color: #2c2e41;
border-radius: 5px;
border: 0;
box-sizing: border-box;
color: rgb(255, 255, 255);
font-size: 12px;
height: 100%;
outline: 0;
padding: 10px 5px 10px 5px;
width: 100%;
font-family: "Fredoka One", cursive;
}
.error-message {
color: red;
}
.trip-form button {
background-color: #fb5666;
border-radius: 12px;
border: 0;
box-sizing: border-box;
color: #eee;
cursor: pointer;
font-size: 18px;
height: 50px;
margin-top: 38px;
outline: 0;
text-align: center;
width: 100%;
}
button span {
position: relative;
z-index: 2;
}
button:after {
position: absolute;
content: "";
top: 0;
left: 0;
width: 0;
height: 100%;
transition: all 2.35s;
}
button:hover {
color: #fff;
}
button:hover:after {
width: 100%;
}
.small_description {
font-size: 60px;
}
Now go to this repo and download all of the SVG files that are available there.
https://github.com/pramit-marattha/road-trip-mapper-mern-app/tree/main/client/src/assets
After you've downloaded all of the svg files, go to the main app component, and begin importing all of the key requirements from the libraries we previously installed, such as ReactMapGl, marker, and popup from the "react-map-gl" library, import all of the components as well as svgs from the assets folder, and finally create four state logEntries whose initial value is empty array, showPopup whose initial value is an empty object, addEntryLocation has a default value of null, and for viewport specify the initial value exactly like the code mentioned below or you can add whatever you want. Create an asynchronous function called getEntries that asynchronously calls the listLogEntries function that was previously established within the api file and whose main task is to retrieve all of the entries made by the users and feed them to the logEntries state and then call that function inside the useEffect() hook by using this Hook, you tell React that your component needs to do something after render.
React will remember the function you passed (we’ll refer to it as our “effect”), and call it later after performing the DOM updates. To this effect, we set the document title, but we could also perform data fetching or call some other imperative API. Placing useEffect() inside the component lets us access the count state variable (or any props) right from the effect. We don’t need a special API to read it — it’s already in the function scope. Hooks embrace JavaScript closures and avoid introducing React-specific APIs where JavaScript already provides a solution.useEffect() the hook is somewhat similar to the life-cycle methods that we are aware of for class components. It runs after every render of the component including the initial render. Hence it can be thought of as a combination of componentDidMount, componentDidUpdate, and componentWillUnmount.If we want to control the behavior of when the effect should run (only on initial render, or only when a particular state variable changes), we can pass in dependencies to the effect to do so. This hook also provides a clean-up option to allow cleaning up of resources before the component is destroyed. basic syntax of the effect: useEffect(didUpdate) .
Create a function named showMarkerPopup and provide the event parameters to it. Inside that function, destruct the latitude and longitude from "event.lngltd" and pass it to addEntryLocation state. Finally, employ all of the imported components within our return statement by simply following the code shown below.
//src/app.js
import * as React from "react";
import { useState, useEffect } from "react";
import ReactMapGL, { Marker, Popup } from "react-map-gl";
import { listLogEntries } from "./api/API";
import MapPinLogo from "./assets/mapperPin.svg";
import MarkerPopup from "./assets/MarkerPopup.svg";
import TripEntryForm from "./components/TripEntryForm";
import ReactStars from "react-rating-stars-component";
import RoadTripNav from "./components/RoadTripNav/RoadTripNav";
const App = () => {
const [logEntries, setLogEntries] = useState([]);
const [showPopup, setShowPopup] = useState({});
const [addEntryLocation, setAddEntryLocation] = useState(null);
const [viewport, setViewport] = useState({
width: "100vw",
height: "100vh",
latitude: 27.7577,
longitude: 85.3231324,
zoom: 7,
});
const getEntries = async () => {
const logEntries = await listLogEntries();
setLogEntries(logEntries);
console.log(logEntries);
};
useEffect(() => {
getEntries();
}, []);
const showMarkerPopup = (event) => {
console.log(event.lngLat);
const [longitude, latitude] = event.lngLat;
setAddEntryLocation({
longitude,
latitude,
});
};
return (
<>
<RoadTripNav />
<ReactMapGL
{...viewport}
mapStyle="mapbox://styles/pramitmarattha/ckiovge5k3e7x17tcmydc42s3"
mapboxApiAccessToken={process.env.REACT_APP_MAPBOX_TOKEN}
onViewportChange={(nextViewport) => setViewport(nextViewport)}
onDblClick={showMarkerPopup}
>
{logEntries.map((entry) => (
<React.Fragment key={entry._id}>
<Marker latitude={entry.latitude} longitude={entry.longitude}>
<div
onClick={() =>
setShowPopup({
// ...showPopup,
[entry._id]: true,
})
}
>
<img
className="map-pin"
style={{
width: `${5 * viewport.zoom}px`,
height: `${5 * viewport.zoom}px`,
}}
src={MapPinLogo}
alt="Map Pin Logo"
/>
</div>
</Marker>
{showPopup[entry._id] ? (
<Popup
latitude={entry.latitude}
longitude={entry.longitude}
closeButton={true}
closeOnClick={false}
dynamicPosition={true}
onClose={() => setShowPopup({})}
anchor="top"
>
<div className="popup">
<ReactStars
count={10}
value={entry.rating}
size={29}
activeColor="#ffd700"
/>
<div className="popup_image">
{entry.image && <img src={entry.image} alt={entry.title} />}
</div>
<h3>{entry.title}</h3>
<p>{entry.comments}</p>
<small>
Visited :{" "}
{new Date(entry.visitDate).toLocaleDateString("en-US", {
weekday: "long",
year: "numeric",
month: "long",
day: "numeric",
})}
</small>
<p>Ratings: {entry.rating}</p>
<div className="small_description">{entry.description}</div>
</div>
</Popup>
) : null}
</React.Fragment>
))}
{addEntryLocation ? (
<>
<Marker
latitude={addEntryLocation.latitude}
longitude={addEntryLocation.longitude}
>
<div>
<img
className="map-pin"
style={{
width: `${8 * viewport.zoom}px`,
height: `${8 * viewport.zoom}px`,
}}
src={MarkerPopup}
alt="Map Pin Logo"
/>
</div>
{/* <div style={{color:"white"}}>{entry.title}</div> */}
</Marker>
<Popup
latitude={addEntryLocation.latitude}
longitude={addEntryLocation.longitude}
closeButton={true}
closeOnClick={false}
dynamicPosition={true}
onClose={() => setAddEntryLocation(null)}
anchor="top"
>
<div className="popup">
<TripEntryForm
onClose={() => {
setAddEntryLocation(null);
getEntries();
}}
location={addEntryLocation}
/>
</div>
</Popup>
</>
) : null}
</ReactMapGL>
</>
);
};
export default App;
The very final step is to add all of the styles to our project, which can be done by going to our previously established styles folder and copying and pasting the following mentioned code into the index.css file.
/* styles/index.css */
@import url("https://fonts.googleapis.com/css2?family=Fredoka+One&family=Poppins:ital,wght@0,200;0,400;1,200;1,300&family=Roboto:ital,wght@0,300;0,400;0,500;1,300;1,400;1,500&display=swap");
body {
margin: 0;
font-family: "Fredoka One", cursive;
height: 100vh;
width: 100vw;
overflow: hidden;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
monospace;
}
.map-pin {
position: absolute;
transform: translate(-50%, -100%);
z-index: -1;
}
.popup {
width: 20vw;
height: auto;
padding: 1rem;
background-color: #8661d1;
border-radius: 5px;
z-index: 999;
}
.popup img {
width: 40%;
height: auto;
border-radius: 5%;
justify-content: center;
align-items: center;
margin: 0 auto;
padding-top: 1rem;
}
.popup_image {
display: flex;
justify-content: center;
align-items: center;
}
.small_description {
font-size: 1.5rem;
color: #fff;
border-radius: 5px;
z-index: 999;
}
button {
border: none;
color: #fa5252;
padding-right: 1rem;
border-radius: 50%;
font-size: 4rem;
margin-top: 0.2rem;
height: auto;
cursor: pointer;
}
Finally, start both the client and the server.
Application up and running
This application's entire source code is available here.
https://github.com/aviyeldevrel/devrel-tutorial-projects/tree/main/MERN-roadtrip-mapper
Main article available here => https://aviyel.com/post/1430
Happy Coding!!
Follow @aviyelHQ or sign-up on Aviyel for early access if you are a project maintainer, contributor, or just an Open Source enthusiast.
Join Aviyel's Discord => Aviyel's world
Twitter =>[https://twitter.com/AviyelHq]