I create MERN stack app and now I want to add Search and filters functions. I created SearchScreen.js and in carRoutes.js added carRouter.get with ‘/search’ query. But the browser said me Cannot read properties of undefined (reading ‘map’) . That is in SearchSreen.js in ‘cars.map’ row. Can you help me ?
I show the code:
SearchScreen.js
import React, { useEffect, useReducer, useState } from 'react';
import { Link, useNavigate, useLocation } from 'react-router-dom';
import axios from 'axios';
import { toast } from 'react-toastify';
import { getError } from '../utils';
import { Helmet } from 'react-helmet-async';
import Row from 'react-bootstrap/Row';
import Col from 'react-bootstrap/Col';
import Rating from '../components/Rating';
import LoadingBox from '../components/LoadingBox';
import MessageBox from '../components/MessageBox';
import Button from 'react-bootstrap/Button';
import Car from '../components/Car';
import LinkContainer from 'react-router-bootstrap/LinkContainer';
const reducer = (state, action) => {
switch (action.type) {
case 'FETCH_REQUEST':
return { ...state, loading: true };
case 'FETCH_SUCCESS':
return {
...state,
cars: action.payload.cars,
page: action.payload.page,
pages: action.payload.pages,
countCars: action.payload.countCars,
loading: false,
};
case 'FETCH_FAIL':
return { ...state, loading: false, error: action.payload };
default:
return state;
}
};
const prices = [
{
name: '$1 to $50',
value: '1-50',
},
{
name: '$51 to $80',
value: '51-80',
},
{
name: '$80 to $160',
value: '80-160',
},
];
export const ratings = [
{
name: '4stars & up',
rating: 4,
},
{
name: '3stars & up',
rating: 3,
},
{
name: '2stars & up',
rating: 2,
},
{
name: '1stars & up',
rating: 1,
},
];
export default function SearchScreen() {
const navigate = useNavigate();
const { search } = useLocation();
const sp = new URLSearchParams(search); // /search?category=Jeep
const category = sp.get('category') || 'all';
const query = sp.get('query') || 'all';
const pricePerHour = sp.get('price Per Hour') || 'all';
const rating = sp.get('rating') || 'all';
const order = sp.get('order') || 'newest';
const page = sp.get('page') || 1;
const [{ loading, error, cars, pages, countCars }, dispatch] =
useReducer(reducer, {
loading: true,
error: '',
});
useEffect(() => {
const fetchData = async () => {
try {
const { data } = await axios.get(
`/api/cars/search?page=${page}&query=${query}&category=${category}&price=${pricePerHour}&rating=${rating}&order=${order}`
);
dispatch({ type: 'FETCH_SUCCESS', payload: data });
} catch (err) {
dispatch({
type: 'FETCH_FAIL',
payload: getError(error),
});
}
};
fetchData();
}, [category, error, order, page, pricePerHour, query, rating]);
const [categories, setCategories] = useState([]);
useEffect(() => {
const fetchCategories = async () => {
try {
const { data } = await axios.get(`/api/cars/categories`);
setCategories(data);
} catch (err) {
toast.error(getError(err));
}
};
fetchCategories();
}, [dispatch]);
const getFilterUrl = (filter, skipPathname) => {
const filterPage = filter.page || page;
const filterCategory = filter.category || category;
const filterQuery = filter.query || query;
const filterRating = filter.rating || rating;
const filterPricePerHour = filter.price || pricePerHour;
const sortOrder = filter.order || order;
return `${
skipPathname ? '' : '/search?'
}category=${filterCategory}&query=${filterQuery}&price=${filterPricePerHour}&rating=${filterRating}&order=${sortOrder}&page=${filterPage}`;
};
return (
<div>
<Helmet>
<title>Search Products</title>
</Helmet>
<Row>
<Col md={3}>
<h3>Department</h3>
<div>
<ul>
<li>
<Link
className={'all' === category ? 'text-bold' : ''}
to={getFilterUrl({ category: 'all' })}
>
Any
</Link>
</li>
{categories.map((c) => (
<li key={c}>
<Link
className={c === category ? 'text-bold' : ''}
to={getFilterUrl({ category: c })}
>
{c}
</Link>
</li>
))}
</ul>
</div>
<div>
<h3>Price</h3>
<ul>
<li>
<Link
className={'all' === pricePerHour ? 'text-bold' : ''}
to={getFilterUrl({ price: 'all' })}
>
Any
</Link>
</li>
{prices.map((p) => (
<li key={p.value}>
<Link
to={getFilterUrl({ pricePerHour: p.value })}
className={p.value === pricePerHour ? 'text-bold' : ''}
>
{p.name}
</Link>
</li>
))}
</ul>
</div>
<div>
<h3>Avg. Customer Review</h3>
<ul>
{ratings.map((r) => (
<li key={r.name}>
<Link
to={getFilterUrl({ rating: r.rating })}
className={`${r.rating}` === `${rating}` ? 'text-bold' : ''}
>
<Rating caption={' & up'} rating={r.rating}></Rating>
</Link>
</li>
))}
<li>
<Link
to={getFilterUrl({ rating: 'all' })}
className={rating === 'all' ? 'text-bold' : ''}
>
<Rating caption={' & up'} rating={0}></Rating>
</Link>
</li>
</ul>
</div>
</Col>
<Col md={9}>
{loading ? (
<LoadingBox></LoadingBox>
) : error ? (
<MessageBox variant="danger">{error}</MessageBox>
) : (
<>
<Row className="justify-content-between mb-3">
<Col md={6}>
<div>
{countCars === 0 ? 'No' : countCars} Results
{query !== 'all' && ' : ' + query}
{category !== 'all' && ' : ' + category}
{pricePerHour !== 'all' && ' : Price ' + pricePerHour}
{rating !== 'all' && ' : Rating ' + rating + ' & up'}
{query !== 'all' ||
category !== 'all' ||
rating !== 'all' ||
pricePerHour !== 'all' ? (
<Button
variant="light"
onClick={() => navigate('/search')}
>
<i className="fas fa-times-circle"></i>
</Button>
) : null}
</div>
</Col>
<Col className="text-end">
Sort by{' '}
<select
value={order}
onChange={(e) => {
navigate(getFilterUrl({ order: e.target.value }));
}}
>
<option value="newest">Newest Arrivals</option>
<option value="lowest">Price: Low to High</option>
<option value="highest">Price: High to Low</option>
<option value="toprated">Avg. Customer Reviews</option>
</select>
</Col>
</Row>
{cars?.length === 0 && (
<MessageBox>No Product Found</MessageBox>
)}
<Row>
{cars.map((car) => (
<Col sm={6} lg={4} className="mb-3" key={car._id}>
<Car car={car}></Car>
</Col>
))}
</Row>
<div>
{[...Array(pages).keys()].map((x) => (
<LinkContainer
key={x + 1}
className="mx-1"
to={{
pathname: '/search',
seacrh: getFilterUrl({ page: x + 1 }, true),
}}
>
<Button
className={Number(page) === x + 1 ? 'text-bold' : ''}
variant="light"
>
{x + 1}
</Button>
</LinkContainer>
))}
</div>
</>
)}
</Col>
</Row>
</div>
);
}
carRoutes.js
import express from 'express';
import Car from '../models/carModel.js';
import expressAsyncHandler from 'express-async-handler';
import { isAuth, isAdmin } from '../utils.js';
const carRouter = express.Router();
carRouter.get('/', async (req,res)=>{
const cars = await Car.find();
res.send(cars);
});
carRouter.get('/search',expressAsyncHandler(async (req,res)=>{
const {query} = req;
const pageSize = query.pageSize || PAGE_SIZE;
const page = query.page || 1;
const category = query.category || '';
const pricePerHour = query.pricePerHour || '';
const rating = query.rating || '';
const order = query.order || '';
const searchQuery = query.query || '';
const queryFilter =
searchQuery && searchQuery !== 'all'
? {
name: {
$regex: searchQuery,
$options: 'i',
},
}
: {};
const categoryFilter = category && category !== 'all' ? { category } : {};
const ratingFilter =
rating && rating !== 'all'
? {
rating: {
$gte: Number(rating),
},
}
: {};
const priceFilter =
pricePerHour && pricePerHour !== 'all'
? {
// 1-50
pricePerHour: {
$gte: Number(pricePerHour.split('-')[0]),
$lte: Number(pricePerHour.split('-')[1]),
},
}
: {};
const sortOrder =
order === 'featured'
? { featured: -1 }
: order === 'lowest'
? { pricePerHour: 1 }
: order === 'highest'
? { pricePerHour: -1 }
: order === 'toprated'
? { rating: -1 }
: order === 'newest'
? { createdAt: -1 }
: { _id: -1 };
const cars = await Car.find({
...queryFilter,
...categoryFilter,
...priceFilter,
...ratingFilter,
})
.sort(sortOrder)
.skip(pageSize * (page - 1))
.limit(pageSize);
const countCars = await Car.countDocuments({
...queryFilter,
...categoryFilter,
...priceFilter,
...ratingFilter,
});
res.send({
cars,
countCars,
page,
pages: Math.ceil(countCars / pageSize),
});
})
);
carRouter.post('/', isAuth, expressAsyncHandler(async (req,res)=> {
const newCar = new Car({
mark: 'sample-name' + Date.now() ,
model: 'sample-name' + Date.now(),
year: 0,
category: 'sample-category',
capacity: 0,
fuelType: 'sample-name',
transmission: 'sample-name',
image: 'sample-image',
pricePerHour: 0,
pricePerDay: 0,
available: true,
description: 'sample description',
});
const car = await newCar.save();
res.send({message: 'Car created', car});
})
);
carRouter.put(
'/:id',
isAuth,
expressAsyncHandler(async (req, res) => {
const carId = req.params.id;
const car = await Car.findById(carId);
if (car) {
car.mark = req.body.mark;
car.model = req.body.model;
car.year = req.body.year;
car.capacity = req.body.capacity;
car.fuelType = req.body.fuelType;
car.transmission = req.body.transmission;
car.pricePerDay = req.body.pricePerDay;
car.pricePerHour = req.body.pricePerHour;
car.image = req.body.image;
car.category = req.body.category;
car.available = req.body.available;
car.description = req.body.description;
await car.save();
res.send({ message: 'Car Updated' });
} else {
res.status(404).send({ message: 'Car Not Found' });
}
})
);
carRouter.delete(
'/:id',
isAuth,
expressAsyncHandler(async (req, res) => {
const car = await Car.findById(req.params.id);
if (car) {
await car.deleteOne();
res.send({ message: 'Car Deleted' });
} else {
res.status(404).send({ message: 'Car Not Found' });
}
})
);
carRouter.post(
'/:id/reviews',
isAuth,
expressAsyncHandler(async (req, res) => {
const carId = req.params.id;
const car = await Car.findById(carId);
if (car) {
if (car.reviews.find((x) => x.name === req.user.name)) {
return res
.status(400)
.send({ message: 'You already submitted a review' });
}
const review = {
name: req.user.name,
rating: Number(req.body.rating),
comment: req.body.comment,
};
car.reviews.push(review);
car.numReviews = car.reviews.length;
car.rating =
car.reviews.reduce((a, c) => c.rating + a, 0) /
car.reviews.length;
const updatedCar = await car.save();
res.status(201).send({
message: 'Review Created',
review: updatedCar.reviews[updatedCar.reviews.length - 1],
numReviews: car.numReviews,
rating: car.rating,
});
} else {
res.status(404).send({ message: 'Car is Not Found' });
}
})
);
const PAGE_SIZE = 3;
carRouter.get(
'/admin',
isAuth , expressAsyncHandler(async (req,res)=>{
const {query} = req;
const page = query.page || 1 ;
const pageSize = query.pageSize || PAGE_SIZE;
const cars = await Car.find()
.skip(pageSize * (page - 1))
.limit(pageSize);
const countCars = await Car.countDocuments();
res.send({
cars,
countCars,
page,
pages: Math.ceil(countCars / pageSize),
})
})
)
carRouter.get('/model/:model', async (req,res)=>{
const car = await Car.findOne({model: req.params.model});
if(car){
res.send(car);
}else{
res.status(404).send({message: 'Car Not Found'});
}
});
carRouter.get('/:id', async (req, res) => {
const car = await Car.findById(req.params.id);
if (car) {
res.send(car);
} else {
res.status(404).send({ message: 'Car Not Found' });
}
});
export default carRouter;
carModel.js
import mongoose from 'mongoose';
const reviewSchema = new mongoose.Schema(
{
name: {type: String, required: true},
comment: {type: String, required:true},
rating: {type: Number, required: true},
},
{
timestamps: true,
}
);
const carSchema = new mongoose.Schema(
{
mark: {type: String, required: true, unique: true},
model: {type: String, required: true, unique: true},
year: {type: Number, required: true},
category: {type: String, required: true},
capacity: {type: Number, required:true},
fuelType: {type: String, required: true},
transmission: {type: String, required: true},
image: {type: String, required: true},
pricePerHour: {type: Number, required: true},
pricePerDay: {type: Number, required: true},
available: {type: Boolean},
location: {
lat: Number,
lng: Number,
address: String,
name: String,
vicinity: String,
googleAddressId: String
},
rating: {type: Number},
numReviews: {type: Number},
description: {type: String, required: true},
reviews: [reviewSchema]
},
{
timestamps: true
}
);
const Car = mongoose.model('Car',carSchema);
export default Car;
2
Answers
You need to make sure that the
cars
variable is an array and has some data in it.To fix this error, you can use the optional chaining operator (?.) to access the
map()
function safely. For example:Or you can also use comparison operators to check if the
cars
variable is defined and has a length before using the map() function. For example:Provide complete and valid initial state to the
useReducer
hook. If you are expecting to be able to destructureloading
,error
,cars
,pages
, andcountCars
from the reducer hook’s state, then the initial state should include all these values.Example:
This way on the initial render cycle before data requests are made, the UI has defined state to render from.
Ensure that all dispatched actions to the reducer function correctly maintain your state invariant.