skip to Main Content

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


  1. 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:

    {cars?.map((data) => (
        // your code here
    ))}
    

    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:

    {cars && cars.length > 0 && cars.map((data) => (
        // your code here
    ))}
    
    Login or Signup to reply.
  2. Provide complete and valid initial state to the useReducer hook. If you are expecting to be able to destructure loading, error, cars, pages, and countCars from the reducer hook’s state, then the initial state should include all these values.

    Example:

    useReducer(reducer, {
      cars: [],
      countCars: 0,
      error: '',
      loading: true,
      pages: [],
    });
    

    This way on the initial render cycle before data requests are made, the UI has defined state to render from.

    const [{ loading, error, cars, pages, countCars }, dispatch] = useReducer(
      reducer,
      {
        cars: [],
        countCars: 0,
        error: '',
        loading: true,
        pages: [],
      }
    );
    
    ...
    
    <Row>
      {cars.map((car) => (
        <Col sm={6} lg={4} className="mb-3" key={car._id}>
          <Car car={car} />
        </Col>
      ))}
    </Row>
    ...
    

    Ensure that all dispatched actions to the reducer function correctly maintain your state invariant.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search