Create a Community Marketplace App using React
In this article, we will explore the process of developing a Community Marketplace application using React, that offers a seamless and user-friendly experience for users. The Community Marketplace App is a platform where users can buy and sell goods. It provides a convenient way for community members to trade items locally.
By following this tutorial, beginners will learn how to use React to build a modern and responsive web application.
Output Preview: Let us have a look at how the final output will look like.
Prerequisites:
Approach to build Community Marketplace App:
- Create a new ReactJS project using a tool like Create React App. Install necessary libraries such as React Router for routing, Bootstrap for styling, and any other libraries needed for state management or API calls.
- Create components for the navbar, product listings, product details, adding new products form, and other necessary components. Organize the components in a structured manner for better maintainability and reusability.
- Allow users to list items they want to sell by creating a form component for adding new products. Include fields for images, descriptions, prices, contact details, etc., in the form for users to fill out when listing their products.
- Implement search functionality to allow users to search for specific products based on keywords. Implement filter functionality to display products from a specific category or based on other criteria chosen by the user.
- Utilize the useState hook to manage the states of various components in the application, such as form data and search/filter criteria. Use React Router to set up routing for different pages of the application, such as the product listings page, product details page, and add product page.
- Utilize useParams hook from React Router to get the Product ID and display details about a specific product on the product details page.
Steps to build Community Marketplace App:
Step 1: Create a reactJS application by using this command
npx create-react-app marketplace
Step 2: Navigate to project directory
cd marketplace
Step 3: Install the necessary packages/libraries in your project using the following commands.
npm install bootstrap
npm install react-router-dom
npm install react-icons --save
npm install react-toastify
Project Structure:
The updated dependencies in package.json file will look like:
"dependencies": {
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-icons": "^5.0.1",
"react-router-dom": "^6.22.3",
"react-scripts": "5.0.1",
"react-toastify": "^10.0.4",
"web-vitals": "^2.1.4"
}
Example: Illustartipon to design a Community Marketplace App with React.
// App.js
import React, { useState, useEffect } from 'react';
import { Route, Routes } from 'react-router-dom';
import { BrowserRouter } from 'react-router-dom'
import 'bootstrap/dist/css/bootstrap.min.css';
import ProductList from './components/ProductList';
import ProductDetail from './components/ProductDetail';
import AddProduct from './components/AddProduct';
import Navbar from './components/Navbar';
const App = () => {
return (
<BrowserRouter>
<Navbar />
<Routes>
<Route path="/" element={<ProductList />} />
<Route path="/addProduct" element={<AddProduct />} />
<Route path="/products/:productId" element={<ProductDetail/>} />
</Routes>
</BrowserRouter>
);
};
export default App;
// Navbar.js
import React from 'react';
import 'bootstrap/dist/css/bootstrap.min.css';
import { Link } from 'react-router-dom';
const Navbar = () => {
return (
<nav className="navbar navbar-expand-lg
navbar-light bg-primary
text-light">
<div className="container">
<a className="navbar-brand text-light
display-6" href="#">
Marketplace
</a>
<button className="navbar-toggler"
type="button" data-toggle="collapse"
data-target="#navbarNav"
aria-controls="navbarNav" aria-expanded="false"
aria-label="Toggle navigation">
<span className="navbar-toggler-icon"></span>
</button>
<div className="collapse navbar-collapse"
id="navbarNav">
<ul className="navbar-nav mr-auto">
<li className="nav-item">
<Link className="nav-link text-light"
to="/">
Products
</Link>
</li>
</ul>
<ul className="navbar-nav">
<li className="nav-item">
<Link className="nav-link text-light"
to="/addProduct">
Sell your Product
</Link>
</li>
</ul>
</div>
</div>
</nav>
);
};
export default Navbar;
// AddProduct.js
import React, { useState } from 'react';
import { toast, ToastContainer } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';
const ProductForm = () => {
const prods = JSON.parse(localStorage.getItem('products')) || [];
const [productName, setProductName] = useState('');
const [description, setDescription] = useState('');
const [price, setPrice] = useState('');
const [image, setImage] = useState(null);
const [id, setId] = useState(prods.length + 1);
const [category, setCategory] = useState('');
const [sellerName, setSellerName] = useState('');
const [contactDetails, setContactDetails] = useState('');
const [products, setProducts] = useState(prods);
const handleProductNameChange = (e) => {
setProductName(e.target.value);
};
const handleDescriptionChange = (e) => {
setDescription(e.target.value);
};
const handlePriceChange = (e) => {
setPrice(e.target.value);
};
const handleImageChange = (e) => {
const file = e.target.files[0];
setImage(file);
};
const handleCategoryChange = (e) => {
setCategory(e.target.value);
};
const handleSellerNameChange = (e) => {
setSellerName(e.target.value);
};
const handleContactDetailsChange = (e) => {
setContactDetails(e.target.value);
};
const handleSubmit = (e) => {
e.preventDefault();
const errors = {};
if (!productName.trim()) {
errors.productName = 'Product Name is required';
}
if (!description.trim()) {
errors.description = 'Description is required';
}
if (!price.trim()) {
errors.price = 'Price is required';
}
if (!category.trim()) {
errors.category = 'Category is required';
}
if (!image) {
errors.image = 'Image is required';
}
if (!sellerName.trim()) {
errors.sellerName = 'Seller Name is required';
}
if (!contactDetails.trim()) {
errors.contactDetails = 'Contact Details are required';
}
if (Object.keys(errors).length === 0) {
// Convert the image file to base64
const reader = new FileReader();
reader.onloadend = () => {
const base64Image = reader.result;
// Create a product object
const product = {
id: id,
productName,
description,
price,
image: base64Image,
quantity: 1,
category,
sellerName,
contactDetails,
};
// Update the local storage
const updatedProducts = [...products, product];
localStorage.setItem(
'products', JSON.stringify(updatedProducts));
// Update the state
setProducts(updatedProducts);
setId(id + 1);
// Reset form fields
setProductName('');
setDescription('');
setPrice('');
setImage(null);
setCategory('');
setSellerName('');
setContactDetails('');
// Show success message
toast.success('Product added successfully');
};
reader.readAsDataURL(image);
} else {
// Show error messages
Object.values(errors).forEach((error) => {
toast.error(error);
});
}
};
return (
<div className="container" style={{ width: '70%' }}>
<h2>Add Product</h2>
<form onSubmit={handleSubmit}>
<div className="form-group">
<label htmlFor="productName">
Product Name:
</label>
<input
type="text"
className="form-control"
id="productName"
value={productName}
onChange={handleProductNameChange}
/>
</div>
<div className="form-group">
<label htmlFor="description">
Description:
</label>
<textarea
className="form-control"
id="description"
value={description}
onChange={handleDescriptionChange}
></textarea>
</div>
<div className="form-group">
<label htmlFor="price">Price:</label>
<input
type="text"
className="form-control"
id="price"
value={price}
onChange={handlePriceChange}
/>
</div>
<div className="form-group">
<label htmlFor="category">Category:</label>
<select
className="form-control"
id="category"
value={category}
onChange={handleCategoryChange}
>
<option value="">Select a category</option>
<option value="tools">Tools</option>
<option value="clothes">Clothing</option>
<option value="sports">Sports</option>
<option value="furniture">Furniture</option>
<option value="other">Other</option>
</select>
</div>
<div className="form-group">
<label htmlFor="sellerName">Seller Name:</label>
<input
type="text"
className="form-control"
id="sellerName"
value={sellerName}
onChange={handleSellerNameChange}
/>
</div>
<div className="form-group">
<label htmlFor="contactDetails">
Contact Details:
</label>
<input
type="text"
className="form-control"
id="contactDetails"
value={contactDetails}
onChange={handleContactDetailsChange}
/>
</div>
<div className="form-group mt-3">
<label htmlFor="image">Image:</label>
<input
type="file"
className="form-control-file"
id="image"
accept="image/*"
onChange={handleImageChange}
/>
</div>
<button type="submit"
className="btn btn-primary mt-3">
Submit
</button>
</form>
<ToastContainer />
</div>
);
};
export default ProductForm;
// ProductList.js
import React, { useState } from 'react';
import { Link } from 'react-router-dom';
import { ToastContainer } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';
const ProductList = () => {
const [searchTerm, setSearchTerm] = useState('');
const [selectedCategory, setSelectedCategory] = useState(null);
const products =
JSON.parse(localStorage.getItem('products')) || [];
const categories =
[...new Set(products.map((product) => product.category))];
const filteredProducts = products.filter(
(product) =>
product.productName
.toLowerCase()
.includes(searchTerm.toLowerCase()) &&
(selectedCategory?product.category === selectedCategory:true)
);
return (
<div className='container mt-3'>
<div className='btn-group mb-3'
role='group'
aria-label='Categories'>
<button
type='button'
className={
`btn ${selectedCategory === null ? 'btn-primary' : 'btn-secondary'}`}
onClick={() => setSelectedCategory(null)}
>
All
</button>
{categories.map((category) => (
<button
key={category}
type='button'
className={
`btn ${selectedCategory === category ? 'btn-primary':'btn-secondary'}`}
onClick={() => setSelectedCategory(category)}>
{category}
</button>
))}
</div>
<input
type='text'
placeholder='Search products...'
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className='form-control mb-3'
/>
<div className='row'>
{filteredProducts.map((product) => (
<div key={product.id}
className='col-md-3 mb-4'>
<div className='card'
style={{ width: '100%' }}>
<img
src={product.image}
className='card-img-top'
alt={product.name}
style={{
objectFit: 'cover',
width: '200px',
height: '200px'
}}
/>
<div className='card-body'>
<h5 className='card-title'
style={{
fontSize: '1.25rem',
fontWeight: 'bold'
}}>
{product.productName}
</h5>
<p className='card-text'
style={{
fontSize: '1rem',
color: 'gray'
}}>
₹{product.price}
</p>
<Link to={`/products/${product.id}`}>
<button className='btn btn-primary'>
View Details
</button>
</Link>
</div>
</div>
</div>
))}
</div>
<ToastContainer />
</div>
);
};
export default ProductList;
// ProductDetail.js
import React, { useState } from 'react';
import { useParams } from 'react-router-dom';
function getProductById(id) {
const products = JSON.parse(
localStorage.getItem('products')) || [];
return products.find(
product => product.id === parseInt(id));
}
function ProductDetail() {
const { productId } = useParams();
const product = getProductById(productId);
const [showContactForm, setShowContactForm] = useState(false);
const [formData, setFormData] = useState({
name: '',
email: '',
message: ''
});
const handleChange = (e) => {
const { name, value } = e.target;
setFormData({ ...formData, [name]: value });
};
const handleSubmit = (e) => {
e.preventDefault();
// Here you can handle the form submission,
// e.g., send an email or store the message in a database
console.log(formData);
setFormData({ name: '', email: '', message: '' });
};
return (
<div className="container mt-3"
style={{
maxWidth: '800px', margin: 'auto',
padding: '20px', border: '1px solid #ccc',
borderRadius: '5px',
boxShadow: '0 2px 4px rgba(0,0,0,0.1)'
}}>
{product ? (
<div>
<img src={product.image}
alt={product.productName}
style={{
maxWidth: '60%', height: 'auto',
marginBottom: '20px'
}} />
<h1 style={{
fontFamily: 'Arial, sans-serif',
fontSize: '24px', fontWeight: 'bold',
marginBottom: '10px'
}}>
{product.productName}
</h1>
<p style={{
fontFamily: 'Arial, sans-serif',
fontSize: '16px', marginBottom: '10px'
}}>
{product.description}
</p>
<p style={{
fontFamily: 'Arial, sans-serif',
fontSize: '16px', marginBottom: '10px'
}}>
Price: ₹
{product.price}
</p>
<p style={{
fontFamily: 'Arial, sans-serif',
fontSize: '16px', marginBottom: '10px'
}}>
Seller: {product.sellerName}
</p>
<p style={{
fontFamily: 'Arial, sans-serif',
fontSize: '16px', marginBottom: '10px'
}}>
Contact Details: {product.contactDetails}
</p>
{!showContactForm && (
<button className="btn btn-primary"
onClick={() => setShowContactForm(true)}>
Contact Seller
</button>
)}
{showContactForm && (
<div>
<h2 style={{
fontFamily: 'Arial, sans-serif',
fontSize: '20px', fontWeight: 'bold',
marginBottom: '10px'
}}>
Contact Seller
</h2>
<form onSubmit={handleSubmit}>
<div className="form-group">
<label htmlFor="name">Name:</label>
<input type="text"
className="form-control"
id="name"
name="name"
value={formData.name}
onChange={handleChange} />
</div>
<div className="form-group">
<label htmlFor="email">
Email:
</label>
<input type="email"
className="form-control"
id="email"
name="email"
value={formData.email}
onChange={handleChange} />
</div>
<div className="form-group">
<label htmlFor="message">
Message:
</label>
<textarea className="form-control"
id="message"
name="message"
value={formData.message}
onChange={handleChange}>
</textarea>
</div>
<button type="submit"
className="btn btn-primary">
Send Message
</button>
</form>
</div>
)}
</div>
) : (
<p>Product not found</p>
)}
</div>
);
}
export default ProductDetail;
Step to Run Application: Run the application using the following command from the root directory of the project
npm start
Output: Your project will be shown in the URL http://localhost:3000/
Contact Us