I am using react and node.js but it seems that I am having a problem in showing the interface in my browser.
I have already changed the Switch to Routes because I am having an error and also instead of useHistory I changed it to useNavigate
App.js
import { BrowserRouter as Router, Route, Routes} from "react-router-dom";
import ProductList from "./components/ProductList";
import AddProduct from "./components/AddProduct";
import EditProduct from "./components/EditProduct";
function App() {
return (
<Router>
<div className="container">
<div className="columns">
<div className="column is-half is-offset-one-quarter">
<Routes>
<Route exact path="/">
<ProductList />
</Route>
<Route path="/add">
<AddProduct />
</Route>
<Route path="/edit/:id">
<EditProduct />
</Route>
</Routes>
</div>
</div>
</div>
</Router>
);
}
export default App;
ProductList.js
import { useState, useEffect } from 'react'
import axios from "axios";
import { Link } from "react-router-dom";
const ProductList = () => {
const [products, setProduct] = useState([]);
useEffect(() => {
getProducts();
}, []);
const getProducts = async () => {
const response = await axios.get('http://localhost:5000/products');
setProduct(response.data);
}
const deleteProduct = async (id) => {
await axios.delete(`http://localhost:5000/products/${id}`);
getProducts();
}
return (
<div>
<Link to="/add" className="button is-primary mt-2">Add New</Link>
<table className="table is-striped is-fullwidth">
<thead>
<tr>
<th>No</th>
<th>Title</th>
<th>Price</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{ products.map((product, index) => (
<tr key={ product.id }>
<td>{ index + 1 }</td>
<td>{ product.title }</td>
<td>{ product.price }</td>
<td>
<Link to={`/edit/${product.id}`} className="button is-small is-info">Edit</Link>
<button onClick={ () => deleteProduct(product.id) } className="button is-small is-danger">Delete</button>
</td>
</tr>
)) }
</tbody>
</table>
</div>
)
}
export default ProductList
AddProduct.js
import { useState } from 'react'
import axios from "axios";
import { useNavigate } from 'react-router-dom';
const AddProduct = () => {
const [title, setTitle] = useState('');
const [price, setPrice] = useState('');
const navigate = useNavigate();
const saveProduct = async (e) => {
e.preventDefault();
await axios.post('http://localhost:5000/products',{
title: title,
price: price
});
navigate.push("/");
}
return (
<div>
<form onSubmit={ saveProduct }>
<div className="field">
<label className="label">Title</label>
<input
className="input"
type="text"
placeholder="Title"
value={ title }
onChange={ (e) => setTitle(e.target.value) }
/>
</div>
<div className="field">
<label className="label">Price</label>
<input
className="input"
type="text"
placeholder="Price"
value={ price }
onChange={ (e) => setPrice(e.target.value) }
/>
</div>
<div className="field">
<button className="button is-primary">Save</button>
</div>
</form>
</div>
)
}
export default AddProduct
EditProduct.js
/* eslint-disable react-hooks/exhaustive-deps */
import { useState, useEffect } from 'react'
import axios from "axios";
import { useNavigate, useParams } from 'react-router-dom';
const EditProduct = () => {
const [title, setTitle] = useState('');
const [price, setPrice] = useState('');
const navigate = useNavigate();
const { id } = useParams();
const updateProduct = async (e) => {
e.preventDefault();
await axios.patch(`http://localhost:5000/products/${id}`,{
title: title,
price: price
});
navigate.push("/");
}
useEffect(() => {
getProductById();
}, []);
const getProductById = async () => {
const response = await axios.get(`http://localhost:5000/products/${id}`);
setTitle(response.data.title);
setPrice(response.data.price);
}
return (
<div>
<form onSubmit={ updateProduct }>
<div className="field">
<label className="label">Title</label>
<input
className="input"
type="text"
placeholder="Title"
value={ title }
onChange={ (e) => setTitle(e.target.value) }
/>
</div>
<div className="field">
<label className="label">Price</label>
<input
className="input"
type="text"
placeholder="Price"
value={ price }
onChange={ (e) => setPrice(e.target.value) }
/>
</div>
<div className="field">
<button className="button is-primary">Update</button>
</div>
</form>
</div>
)
}
export default EditProduct
I am still new in using nodejs and react, and after editing the app.js it started not showing anything.
So i am trying to implement like and dislike functionality, whenever someone clicks the like button the like array is updated but the count of the array is shown in the ui if refresh not before that
import React, { useEffect, useState } from "react";
import axios from "axios";
import * as timeago from "timeago.js";
import { Link } from "react-router-dom";
import MoreVertRoundedIcon from "#mui/icons-material/MoreVertRounded";
import ThumbUpOutlinedIcon from "#mui/icons-material/ThumbUpOutlined";
import ThumbUpAltRoundedIcon from "#mui/icons-material/ThumbUpAltRounded";
import ThumbDownOffAltRoundedIcon from "#mui/icons-material/ThumbDownOffAltRounded";
import ThumbDownAltRoundedIcon from "#mui/icons-material/ThumbDownAltRounded";
import CommentRoundedIcon from "#mui/icons-material/CommentRounded";
import { useContext } from "react";
import { AuthContext } from "../../context/AuthContext";
const Post = ({ post }) => {
const [user, setUser] = useState({});
const { user: currentUser } = useContext(AuthContext);
const likePost = async () => {
try {
await axios.put(`/post/${post?._id}/like`, { userId: currentUser._id });
} catch (error) {}
};
const dislikePost = async () => {
try {
await axios.put(`/post/${post?._id}/dislike`, {
userId: currentUser._id,
});
} catch (error) {}
};
useEffect(() => {
const fetchUser = async () => {
const response = await axios.get(`/users?userId=${post.userId}`);
setUser(response.data);
};
fetchUser();
}, [post.userId]);
return (
<div className="post">
<div className="post-top">
<div className="user">
<Link
to={`/profile/${user?.username}`}
style={{ color: "inherit", textDecoration: "none" }}
>
<img
src={
user?.profilePicture
? user?.profilePicture
: "https://thumbs.dreamstime.com/b/no-user-profile-picture-24185395.jpg"
}
alt=""
/>
</Link>
<div className="user-info">
<Link
to={`/profile/${user?.username}`}
style={{ color: "inherit", textDecoration: "none" }}
>
<span className="username">{user?.username}</span>
</Link>
<span>{timeago.format(post.createdAt)}</span>
</div>
</div>
<div>
<MoreVertRoundedIcon style={{ cursor: "pointer" }} />
</div>
</div>
{post?.image ? <hr /> : <hr style={{ display: "none" }} />}
<div className="post-center">
{post?.image ? (
<img src={post?.image} alt="" />
) : (
<img style={{ display: "none" }} />
)}
</div>
<div className="post-desc">
<div className="description">
<span>{user?.username}</span>
<span>{post?.description}</span>
</div>
</div>
<div className="post-bottom">
<div className="icons" onClick={likePost}>
{post?.likes.includes(currentUser?._id) ? <ThumbUpAltRoundedIcon /> : <ThumbUpOutlinedIcon /> }
<span>{post?.likes.length}</span>
</div>
<div className="icons" onClick={dislikePost}>
{post?.likes.includes(currentUser?._id) ? <ThumbDownAltRoundedIcon /> : <ThumbDownOffAltRoundedIcon />}
<span>{post?.dislikes.length}</span>
</div>
<div className="icons">
<CommentRoundedIcon />
<span>10</span>
</div>
</div>
</div>
);
};
export default Post;
I tried using use effects but i think i did something wrong in the use effect, and i am only using react context api not redux
There're 2 solutions:
1- If your put endpoint returns the whole post information after liking or disliking a post, you need to create new state to hold your post information as follow and update it accordingly:
import React, { useEffect, useState } from 'react';
import axios from 'axios';
import * as timeago from 'timeago.js';
import { Link } from 'react-router-dom';
import MoreVertRoundedIcon from '#mui/icons-material/MoreVertRounded';
import ThumbUpOutlinedIcon from '#mui/icons-material/ThumbUpOutlined';
import ThumbUpAltRoundedIcon from '#mui/icons-material/ThumbUpAltRounded';
import ThumbDownOffAltRoundedIcon from '#mui/icons-material/ThumbDownOffAltRounded';
import ThumbDownAltRoundedIcon from '#mui/icons-material/ThumbDownAltRounded';
import CommentRoundedIcon from '#mui/icons-material/CommentRounded';
import { useContext } from 'react';
import { AuthContext } from '../../context/AuthContext';
const Post = ({ post }) => {
const [user, setUser] = useState({}),
[currentPost, setCurrentPost] = useState(post);
const { user: currentUser } = useContext(AuthContext);
const likePost = async () => {
try {
const res = await axios.put(`/post/${currentPost?._id}/like`, { userId: currentUser._id });
setCurrentPost(res.data);
} catch (error) {
console.log(error);
}
};
const dislikePost = async () => {
try {
const res = await axios.put(`/post/${currentPost?._id}/dislike`, {
userId: currentUser._id,
});
setCurrentPost(res.data);
} catch (error) {
console.log(error);
}
};
useEffect(() => {
const fetchUser = async () => {
const response = await axios.get(`/users?userId=${currentPost.userId}`);
setUser(response.data);
};
fetchUser();
}, [currentPost.userId]);
return (
<div className="post">
<div className="post-top">
<div className="user">
<Link
to={`/profile/${user?.username}`}
style={{ color: 'inherit', textDecoration: 'none' }}
>
<img
src={
user?.profilePicture
? user?.profilePicture
: 'https://thumbs.dreamstime.com/b/no-user-profile-picture-24185395.jpg'
}
alt=""
/>
</Link>
<div className="user-info">
<Link
to={`/profile/${user?.username}`}
style={{ color: 'inherit', textDecoration: 'none' }}
>
<span className="username">{user?.username}</span>
</Link>
<span>{timeago.format(currentPost.createdAt)}</span>
</div>
</div>
<div>
<MoreVertRoundedIcon style={{ cursor: 'pointer' }} />
</div>
</div>
{currentPost?.image ? <hr /> : <hr style={{ display: 'none' }} />}
<div className="post-center">
{currentPost?.image ? (
<img src={currentPost?.image} alt="" />
) : (
<img style={{ display: 'none' }} />
)}
</div>
<div className="post-desc">
<div className="description">
<span>{user?.username}</span>
<span>{currentPost?.description}</span>
</div>
</div>
<div className="post-bottom">
<div className="icons" onClick={likePost}>
{currentPost?.likes.includes(currentUser?._id) ? (
<ThumbUpAltRoundedIcon />
) : (
<ThumbUpOutlinedIcon />
)}
<span>{currentPost?.likes.length}</span>
</div>
<div className="icons" onClick={dislikePost}>
{currentPost?.likes.includes(currentUser?._id) ? (
<ThumbDownAltRoundedIcon />
) : (
<ThumbDownOffAltRoundedIcon />
)}
<span>{currentPost?.dislikes.length}</span>
</div>
<div className="icons">
<CommentRoundedIcon />
<span>10</span>
</div>
</div>
</div>
);
};
export default Post;
2- If you have an endpoint which fetches the required post information you can update your code as follows:
import React, { useEffect, useState } from 'react';
import axios from 'axios';
import * as timeago from 'timeago.js';
import { Link } from 'react-router-dom';
import MoreVertRoundedIcon from '#mui/icons-material/MoreVertRounded';
import ThumbUpOutlinedIcon from '#mui/icons-material/ThumbUpOutlined';
import ThumbUpAltRoundedIcon from '#mui/icons-material/ThumbUpAltRounded';
import ThumbDownOffAltRoundedIcon from '#mui/icons-material/ThumbDownOffAltRounded';
import ThumbDownAltRoundedIcon from '#mui/icons-material/ThumbDownAltRounded';
import CommentRoundedIcon from '#mui/icons-material/CommentRounded';
import { useContext } from 'react';
import { AuthContext } from '../../context/AuthContext';
const Post = ({ post }) => {
const [user, setUser] = useState({}),
[currentPost, setCurrentPost] = useState(post);
const { user: currentUser } = useContext(AuthContext);
const updatePostData = async () => {
try {
const res = await axios.get(`/post/${currentPost?._id}`, { userId: currentUser._id });
setCurrentPost(res.data);
} catch (error) {
console.log(error);
}
};
const likePost = async () => {
try {
await axios.put(`/post/${currentPost?._id}/like`, { userId: currentUser._id });
await updatePostData();
} catch (error) {
console.log(error);
}
};
const dislikePost = async () => {
try {
await axios.put(`/post/${currentPost?._id}/dislike`, {
userId: currentUser._id,
});
await updatePostData();
} catch (error) {
console.log(error);
}
};
useEffect(() => {
const fetchUser = async () => {
const response = await axios.get(`/users?userId=${currentPost.userId}`);
setUser(response.data);
};
fetchUser();
}, [currentPost.userId]);
return (
<div className="post">
<div className="post-top">
<div className="user">
<Link
to={`/profile/${user?.username}`}
style={{ color: 'inherit', textDecoration: 'none' }}
>
<img
src={
user?.profilePicture
? user?.profilePicture
: 'https://thumbs.dreamstime.com/b/no-user-profile-picture-24185395.jpg'
}
alt=""
/>
</Link>
<div className="user-info">
<Link
to={`/profile/${user?.username}`}
style={{ color: 'inherit', textDecoration: 'none' }}
>
<span className="username">{user?.username}</span>
</Link>
<span>{timeago.format(currentPost.createdAt)}</span>
</div>
</div>
<div>
<MoreVertRoundedIcon style={{ cursor: 'pointer' }} />
</div>
</div>
{currentPost?.image ? <hr /> : <hr style={{ display: 'none' }} />}
<div className="post-center">
{currentPost?.image ? (
<img src={currentPost?.image} alt="" />
) : (
<img style={{ display: 'none' }} />
)}
</div>
<div className="post-desc">
<div className="description">
<span>{user?.username}</span>
<span>{currentPost?.description}</span>
</div>
</div>
<div className="post-bottom">
<div className="icons" onClick={likePost}>
{currentPost?.likes.includes(currentUser?._id) ? (
<ThumbUpAltRoundedIcon />
) : (
<ThumbUpOutlinedIcon />
)}
<span>{currentPost?.likes.length}</span>
</div>
<div className="icons" onClick={dislikePost}>
{currentPost?.likes.includes(currentUser?._id) ? (
<ThumbDownAltRoundedIcon />
) : (
<ThumbDownOffAltRoundedIcon />
)}
<span>{currentPost?.dislikes.length}</span>
</div>
<div className="icons">
<CommentRoundedIcon />
<span>10</span>
</div>
</div>
</div>
);
};
export default Post;
i was trying to make a restaurant review application using mern stack but in the frontend , i keep getting a problem in the react.
The pages load perfectly in the main page where the code as follows
import React, { useState, useEffect } from "react";
import RestaurantDataService from "../services/restaurant";
import { Link } from "react-router-dom";
const RestaurantsList = props => {
const [restaurants, setRestaurants] = useState([]);
const [searchName, setSearchName ] = useState("");
const [searchZip, setSearchZip ] = useState("");
const [searchCuisine, setSearchCuisine ] = useState("");
const [cuisines, setCuisines] = useState(["All Cuisines"]);
useEffect(() => {
retrieveRestaurants();
retrieveCuisines();
}, []);
const onChangeSearchName = e => {
const searchName = e.target.value;
setSearchName(searchName);
};
const onChangeSearchZip = e => {
const searchZip = e.target.value;
setSearchZip(searchZip);
};
const onChangeSearchCuisine = e => {
const searchCuisine = e.target.value;
setSearchCuisine(searchCuisine);
};
const retrieveRestaurants = () => {
RestaurantDataService.getAll()
.then(response => {
console.log(response.data);
setRestaurants(response.data.restaurants);
})
.catch(e => {
console.log(e);
});
};
const retrieveCuisines = () => {
RestaurantDataService.getCuisines()
.then(response => {
console.log(response.data);
setCuisines(["All Cuisines"].concat(response.data));
})
.catch(e => {
console.log(e);
});
};
const refreshList = () => {
retrieveRestaurants();
};
const find = (query, by) => {
RestaurantDataService.find(query, by)
.then(response => {
console.log(response.data);
setRestaurants(response.data.restaurants);
})
.catch(e => {
console.log(e);
});
};
const findByName = () => {
find(searchName, "name")
};
const findByZip = () => {
find(searchZip, "zipcode")
};
const findByCuisine = () => {
if (searchCuisine === "All Cuisines") {
refreshList();
} else {
find(searchCuisine, "cuisine")
}
};
return (
<div>
<div className="row pb-1">
<div className="input-group col-lg-4">
<input
type="text"
className="form-control"
placeholder="Search by name"
value={searchName}
onChange={onChangeSearchName}
/>
<div className="input-group-append">
<button
className="btn btn-outline-secondary"
type="button"
onClick={findByName}
>
Search
</button>
</div>
</div>
<div className="input-group col-lg-4">
<input
type="text"
className="form-control"
placeholder="Search by zip"
value={searchZip}
onChange={onChangeSearchZip}
/>
<div className="input-group-append">
<button
className="btn btn-outline-secondary"
type="button"
onClick={findByZip}
>
Search
</button>
</div>
</div>
<div className="input-group col-lg-4">
<select onChange={onChangeSearchCuisine}>
{cuisines.map(cuisine => {
return (
<option value={cuisine}> {cuisine.substr(0, 20)} </option>
)
})}
</select>
<div className="input-group-append">
<button
className="btn btn-outline-secondary"
type="button"
onClick={findByCuisine}
>
Search
</button>
</div>
</div>
</div>
<div className="row">
{restaurants.map((restaurant) => {
const address = `${restaurant.address.building} ${restaurant.address.street}, ${restaurant.address.zipcode}`;
return (
<div className="col-lg-4 pb-1">
<div className="card">
<div className="card-body">
<h5 className="card-title">{restaurant.name}</h5>
<p className="card-text">
<strong>Cuisine: </strong>{restaurant.cuisine}<br/>
<strong>Address: </strong>{address}
</p>
<div className="row">
<Link to={"/restaurants/"+restaurant._id} className="btn btn-primary col-lg-5 mx-1 mb-1">
View Reviews
</Link>
<a target="_blank" href={"https://www.google.com/maps/place/" + address} className="btn btn-primary col-lg-5 mx-1 mb-1">View Map</a>
</div>
</div>
</div>
</div>
);
})}
</div>
</div>
);
};
export default RestaurantsList;
but the problem arise when i click the view review button in the card shown in this page where the screen does not show anything and recieve anything kind of data. the code as follows:
import React, { useState, useEffect } from "react";
import RestaurantDataService from "../services/restaurant";
import { Link } from "react-router-dom";
const Restaurant = props => {
const initialRestaurantState = {
id: null,
name: "",
address: {},
cuisine: "",
reviews: []
};
const [restaurant, setRestaurant] = useState(initialRestaurantState);
const getRestaurant = id => {
RestaurantDataService.get(id)
.then(response => {
setRestaurant(response.data);
console.log(response.data);
})
.catch(e => {
console.log(e);
});
};
useEffect(() => {
getRestaurant(props.match.params.id);
}, [props.match.params.id]);
const deleteReview = (reviewId, index) => {
RestaurantDataService.deleteReview(reviewId, props.user.id)
.then(response => {
setRestaurant((prevState) => {
prevState.reviews.splice(index, 1)
return({
...prevState
})
})
})
.catch(e => {
console.log(e);
});
};
return (
<div>
{restaurant ? (
<div>
<h5>{restaurant.name}</h5>
<p>
<strong>Cuisine: </strong>{restaurant.cuisine}<br/>
<strong>Address: </strong>{restaurant.address.building} {restaurant.address.street}, {restaurant.address.zipcode}
</p>
<Link to={"/restaurants/" + props.match.params.id + "/review"} className="btn btn-primary">
Add Review
</Link>
<h4> Reviews </h4>
<div className="row">
{restaurant.reviews.length > 0 ? (
restaurant.reviews.map((review, index) => {
return (
<div className="col-lg-4 pb-1" key={index}>
<div className="card">
<div className="card-body">
<p className="card-text">
{review.text}<br/>
<strong>User: </strong>{review.name}<br/>
<strong>Date: </strong>{review.date}
</p>
{props.user && props.user.id === review.user_id &&
<div className="row">
<a onClick={() => deleteReview(review._id, index)} className="btn btn-primary col-lg-5 mx-1 mb-1">Delete</a>
<Link to={{
pathname: "/restaurants/" + props.match.params.id + "/review",
state: {
currentReview: review
}
}} className="btn btn-primary col-lg-5 mx-1 mb-1">Edit</Link>
</div>
}
</div>
</div>
</div>
);
})
) : (
<div className="col-sm-4">
<p>No reviews yet.</p>
</div>
)}
</div>
</div>
) : (
<div>
<br />
<p>No restaurant selected.</p>
</div>
)}
</div>
);
};
export default Restaurant;
please kindly answer the question if possible
I was doing a Node - React course in Coursera, the course is really outdated so i decided to follow the course but update the code to current versions of frameworks and technologies. The project was to build up a restaurant web app, all the app is working well but the part showing the menu is supposed to render a specific dish when clicked, but when done, the app crashes showing the following error. The code of the route in the server is:
dishRouter.js
const bodyParser = require('body-parser');
const express = require('express');
const cors = require('./cors');
const authenticate = require('../authenticate');
const Dishes = require('../models/dishes');
.
.
.
dishRouter.route('/:dishId')
.options(
cors.corsWithOptions,
(req, res) => {
res.sendStatus(200);
}
)
.get(
cors.cors,
(req, res, next) => {
Dishes.findById(req.params.dishId)
.populate('comments.author')
.then((dish) => {
console.log('Dish Created ', dish);
res.statusCode = 200;
res.setHeader('Content-Type', 'application/json');
res.json(dish);
}, (err) => next(err))
.catch((err) => next(err));
}
)
.post(
cors.corsWithOptions,
authenticate.verifyUser,
authenticate.verifyAdmin,
(req, res, _next) => {
res.statusCode = 403;
res.end(`POST operation not supported on /dishes/${req.params.dishId}`);
}
)
.put(
cors.corsWithOptions,
authenticate.verifyUser,
authenticate.verifyAdmin,
(req, res, next) => {
Dishes.findByIdAndUpdate(req.params.dishId, {
$set: req.body
}, { new: true })
.then((dish) => {
console.log('Dish Created ', dish);
res.statusCode = 200;
res.setHeader('Content-Type', 'application/json');
res.json(dish);
}, (err) => next(err))
.catch((err) => next(err));
}
)
.delete(
cors.corsWithOptions,
authenticate.verifyUser,
authenticate.verifyAdmin,
(req, res, next) => {
Dishes.findByIdAndRemove(req.params.dishId)
.then((resp) => {
res.statusCode = 200;
res.setHeader('Content-Type', 'application/json');
res.json(resp);
}, (err) => next(err))
.catch((err) => next(err));
}
);
module.exports = dishRouter;
the router dishRouter.route('/:dishId') is the responsible to handle the specific dish information
The client side code
mainComponent.js
import React, { Component } from 'react';
import Home from './HomeComponent';
import Menu from './MenuComponent';
import Contact from './ContactComponent';
import DishDetail from './DishdetailComponent';
import Favorites from './FavoriteComponent';
import Header from './HeaderComponent';
import Footer from './FooterComponent';
import About from './AboutComponent'
import {Routes, Route, Navigate, useParams, useNavigate, useLocation} from 'react-router-dom'; //Switch changed to routes Also redirect is changed to Navigate since version 6
import {connect} from 'react-redux';
import { postComment, postFeedback,fetchDishes, fetchComments, fetchPromos, fetchLeaders,loginUser, logoutUser, fetchFavorites, postFavorite, deleteFavorite } from '../redux/ActionCreators';
import { actions } from 'react-redux-form';
import {TransitionGroup, CSSTransition} from 'react-transition-group';
// --------Hook to use withRouter from v5 in actual v6
export const withRouter = (Component) => {
const Wrapper = (props) => {
const history = useNavigate();
return (
<Component
history={history}
{...props}
/>
);
};
return Wrapper;
};
//------------End of Hook
const mapStateToProps = (state) => {
return{
dishes: state.dishes,
comments: state.comments,
promotions: state.promotions,
leaders: state.leaders,
favorites: state.favorites,
auth: state.auth
}
}
const mapDispatchToProps = dispatch => ({
postComment: (dishId, rating, author, comment) => dispatch(postComment(dishId, rating, author, comment)),
postFeedback: (firstName, lastName, telnum, email, agree, contactType, message) => dispatch(postFeedback(firstName, lastName, telnum, email, agree, contactType, message)),
fetchDishes: () => { dispatch(fetchDishes())},
fetchComments: () => dispatch(fetchComments()),
fetchPromos: () => dispatch(fetchPromos()),
fetchLeaders: () => dispatch(fetchLeaders()),
resetFeedbackForm: () => { dispatch(actions.reset('feedback'))},
loginUser: (creds) => dispatch(loginUser(creds)),
logoutUser: () => dispatch(logoutUser()),
fetchFavorites: () => dispatch(fetchFavorites()),
postFavorite: (dishId) => dispatch(postFavorite(dishId)),
deleteFavorite: (dishId) => dispatch(deleteFavorite(dishId))
});
class Main extends Component {
componentDidMount() {
this.props.fetchDishes();
this.props.fetchComments();
this.props.fetchPromos();
this.props.fetchLeaders();
this.props.fetchFavorites();
}
render(){
const HomePage = () => {
// console.log(this.props)
return(
<Home
dish={this.props.dishes.dishes.filter((dish) => dish.featured)[0]}
dishesLoading={this.props.dishes.isLoading}
dishesErrMess={this.props.dishes.errMess}
promotion={this.props.promotions.promotions.filter((promo) => promo.featured)[0]}
promoLoading={this.props.promotions.isLoading}
promoErrMess={this.props.promotions.errMess}
leader={this.props.leaders.leaders.filter((leader) => leader.featured)[0]}
leaderLoading={this.props.leaders.isLoading}
leaderErrMess={this.props.leaders.errMess}
/>
);
}
const DishWithId = () => {
let params = useParams();
return(
this.props.auth.isAuthenticated
?
<DishDetail dish={this.props.dishes.dishes.filter((dish) => dish._id === parseInt(params.dishId,10))[0]}
isLoading={this.props.dishes.isLoading}
errMess={this.props.dishes.errMess}
comments={this.props.comments.comments.filter((comment) => comment.dishId === parseInt(params.dishId,10))}
commentsErrMess={this.props.comments.errMess}
postComment={this.props.postComment}
favorite={this.props.favorites.favorites.dishes.some((dish) => dish._id === params.dishId)}
postFavorite={this.props.postFavorite}
/>
:
<DishDetail dish={this.props.dishes.dishes.filter((dish) => dish._id === parseInt(params.dishId,10))[0]}
isLoading={this.props.dishes.isLoading}
errMess={this.props.dishes.errMess}
comments={this.props.comments.comments.filter((comment) => comment.dishId === parseInt(params.dishId,10))}
commentsErrMess={this.props.comments.errMess}
postComment={this.props.postComment}
favorite={false}
postFavorite={this.props.postFavorite}
/>
);
};
const AnimatedSwitch = () => {
const location = useLocation();
console.log("location", location);
console.log("Props", this.props);
return (
<TransitionGroup>
<CSSTransition key ={location.key} classNames='page' timeout = {300}>
<Routes>
<Route path="/home" element={<HomePage/>}/> {/*Also component must be changed to element since i am using V6*/}
<Route exact path="/menu" element= {<Menu dishes={this.props.dishes} />} /> {/* in previous version this must look as: () => element= {<Menu dishes={this.props.dishes} />} */}
<Route path = "/menu/:dishId" element={<DishWithId />} />
{/* <PrivateRoute exact path="/favorites" component={() => <Favorites favorites={this.props.favorites} deleteFavorite={this.props.deleteFavorite} />} /> */}
<Route exact path ='/contactus' element={<Contact postFeedback={this.props.postFeedback} resetFeedbackForm={this.props.resetFeedbackForm}/>} />
<Route path = '/aboutus' element={<About leaders = {this.props.leaders}/>} />
<Route path="*"element={<Navigate to="/home" />} />
{/* Instead of redirect the above snippet is needed to redirect if there is no matched url */}
</Routes>
</CSSTransition>
</TransitionGroup>
);
};
return (
<div>
<Header
auth={this.props.auth}
loginUser={this.props.loginUser}
logoutUser={this.props.logoutUser} />
<AnimatedSwitch/>
<Footer/>
</div>
);
}
};
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Main));
menuComponent.js
import React from 'react';
import { Card, CardImg, CardImgOverlay,CardTitle,Breadcrumb, BreadcrumbItem} from 'reactstrap';
import {Link} from 'react-router-dom';
import { Loading } from './LoadingComponent';
import {baseUrl} from '../shared/baseUrl';
function RenderMenuItem ({dish, onClick}) {
return (
<Card>
<Link to={ `/menu/${dish._id}` } >
<CardImg width="100%" src={baseUrl + dish.image} alt={dish.name} />
<CardImgOverlay>
<CardTitle>{dish.name}</CardTitle>
</CardImgOverlay>
</Link>
</Card>
);
}
const Menu = (props) =>{
const menu = props.dishes.dishes.map((dish) => { // antes era this.props.dishes pero ahora props pasa como parametro a la funcion por lo cual this no es necesario
return (
<div key={dish._id} id = 'dishes' className="col-12 col-md-5 m-1">
<RenderMenuItem dish={dish}/>
</div>
);
});
if (props.dishes.isLoading){
return(
<div className="container">
<div className="row">
<Loading />
</div>
</div>
);
}
else if(props.dishes.errMess){
return(
<div className="container">
<div className="row">
<h4>{props.dishes.errMess}</h4>
</div>
</div>
);
}
else
return (
<div className="container">
<div className="row">
<Breadcrumb>
<BreadcrumbItem><Link to="/home">Home</Link></BreadcrumbItem>
<BreadcrumbItem active>Menu</BreadcrumbItem>
</Breadcrumb>
<div className="col-12">
<h3>Menu</h3>
<hr />
</div>
</div>
<div className="row">
{menu}
</div>
</div>
);
}
export default Menu;
dishdetailComponent.js
import React, {Component} from 'react';
import { Card, CardImg,CardImgOverlay, CardText, CardBody, CardTitle,
Breadcrumb,BreadcrumbItem, Button,
Modal, ModalHeader, ModalBody,
Label, Col, Row} from 'reactstrap';
import {Link} from 'react-router-dom'
import { Control, LocalForm, Errors } from 'react-redux-form';
import { Loading } from './LoadingComponent';
import { baseUrl } from '../shared/baseUrl';
import {FadeTransform, Fade, Stagger} from 'react-animation-components';
/**........................ comment component ends ................................................. */
//// validators
const required = (val) => val && val.length; //value > 0
const maxLength = (len) => (val) => !(val) || (val.length <= len);
const minLength = (len) => (val) => (val) && (val.length >= len);
class CommentForm extends Component {
constructor(props) {
super(props);
this.state = {
isCommentFormModalOpen: false
};
this.toggleCommentFormModal = this.toggleCommentFormModal.bind(this);
this.handleCommentFormSubmit = this.handleCommentFormSubmit.bind(this);
}
handleCommentFormSubmit(values) {
this.props.postComment(this.props.dishId, values.rating, values.author, values.comment);
}
toggleCommentFormModal() {
this.setState({
isCommentFormModalOpen: !this.state.isCommentFormModalOpen
});
}
render() {
return (
<React.Fragment>
<Button outline onClick={this.toggleCommentFormModal}>
<span className="fa fa-comments fa-lg"></span> Submit Comment
</Button>
{/* commentform Modal */}
<Modal isOpen={this.state.isCommentFormModalOpen} toggle={this.toggleCommentFormModal} >
<ModalHeader toggle={this.toggleCommentFormModal}> Submit Comment </ModalHeader>
<ModalBody>
<LocalForm onSubmit={(values) => this.handleCommentFormSubmit(values)}>
{/* rating */}
<Row className="form-group">
<Label htmlFor="rating" md={12} >Rating</Label>
<Col md={12}>
<Control.select model=".rating"
className="form-control"
name="rating"
id="rating"
validators={{
required
}}
>
<option>Please Select</option>
<option>1</option>
<option>2</option>
<option>3</option>
<option>4</option>
<option>5</option>
</Control.select>
<Errors
className="text-danger"
model=".author"
show="touched"
messages={{
required: 'Required',
}}
/>
</Col>
</Row>
{/* author */}
<Row className="form-group">
<Label htmlFor="author" md={12}> Your Name </Label>
<Col md={12}>
<Control.text model=".author" id="author" name="author"
placeholder="First Name"
className="form-control"
validators={{
required, minLength: minLength(3), maxLength: maxLength(15)
}}
/>
<Errors
className="text-danger"
model=".author"
show="touched"
messages={{
required: 'Required',
minLength: 'Must be greater than 2 characters',
maxLength: 'Must be 15 characters or less'
}}
/>
</Col>
</Row>
{/* comment */}
<Row className="form-group">
<Label htmlFor="comment" md={12}>Comment</Label>
<Col md={12}>
<Control.textarea model=".comment" id="comment" name="comment"
rows="6"
className="form-control"
validators={{
required
}}
/>
<Errors
className="text-danger"
model=".author"
show="touched"
messages={{
required: 'Required',
}}
/>
</Col>
</Row>
{/* submit button */}
<Row className="form-group">
<Col>
<Button type="submit" color="primary">
Submit
</Button>
</Col>
</Row>
</LocalForm>
</ModalBody>
</Modal>
</React.Fragment>
);
}
}
/**........................ comment component ends ................................................. */
function RenderDish({dish,favorite, postFavorite}){
if (dish != null){
return(
<div className='col-12 col-md-5 m-1'>
<FadeTransform in
transformProps={{
exitTransform: 'scale(0.5) translateY(-50%)'
}}>
<Card>
<CardImg width="100%" src={baseUrl + dish.image} alt={dish.name}/>
<CardImgOverlay>
<Button outline color="primary" onClick={() => favorite ? console.log('Already favorite') : postFavorite(dish._id)}>
{favorite ?
<span className="fa fa-heart"></span>
:
<span className="fa fa-heart-o"></span>
}
</Button>
</CardImgOverlay>
<CardBody>
<CardTitle>{dish.name}</CardTitle>
<CardText>{dish.description}</CardText>
</CardBody>
</Card>
</FadeTransform>
</div>
);
}
else{
return(
<div></div>
);
}
}
function RenderComments({comments,postComment,dishId}){
if(comments != null){
const review = comments.map((comment) =>{
return (
<Stagger in >
<div key = {comment._id} className='container'>
<Fade in>
<li id='comments'>
<p>{comment.comment}</p>
<p>--{comment.author.firstname} {comment.author.lastname} , {new Intl.DateTimeFormat('en-US',{year: 'numeric', month:'short', day:'2-digit'}).format(new Date(Date.parse(comment.date)))}</p>
</li>
</Fade>
</div>
</Stagger>
);
});
return(
<div className='col-12 col-md-5 m-1'>
{review}
<CommentForm dishId={dishId} postComment={postComment} />
</div>
)
}
}
const DishDetail = (props)=> {
if (props.isLoading){
return(
<div className="container">
<div className="row">
<Loading />
</div>
</div>
);
}
else if(props.errMess){
return(
<div className="container">
<div className="row">
<h4>{props.errMess}</h4>
</div>
</div>
);
}
else if (props.dish != null){
return (
<div className="container">
<div className="row">
<Breadcrumb>
<BreadcrumbItem><Link to="/menu">Menu</Link></BreadcrumbItem>
<BreadcrumbItem active>{props.dish.name}</BreadcrumbItem>
</Breadcrumb>
<div className="col-12">
<h3>{props.dish.name}</h3>
<hr />
</div>
</div>
<div className="row">
<RenderDish dish={props.dish} favorite={props.favorite} postFavorite={props.postFavorite}/>
<RenderComments comments={props.comments}
postComment={props.postComment}
dishId={props.dish._id} />
</div>
</div>
);
}
else{
return(
<div></div>
);
}
}
export default DishDetail;
Also the favorites is a feature to add favorites dishes to a user.
from client side.:
favoriteComponent.js
import React, { Component } from 'react';
import { Media, Breadcrumb, BreadcrumbItem, Button } from 'reactstrap';
import { Link } from 'react-router-dom';
import { baseUrl } from '../shared/baseUrl';
import { Loading } from './LoadingComponent';
function RenderMenuItem({ dish, deleteFavorite }) {
return(
<Media tag="li">
<Media left middle>
<Media object src={baseUrl + dish.image} alt={dish.name} />
</Media>
<Media body className="ml-5">
<Media heading>{dish.name}</Media>
<p>{dish.description}</p>
<Button outline color="danger" onClick={() => deleteFavorite(dish._id)}>
<span className="fa fa-times"></span>
</Button>
</Media>
</Media>
);
}
const Favorites = (props) => {
if (props.favorites.isLoading) {
return(
<div className="container">
<div className="row">
<Loading />
</div>
</div>
);
}
else if (props.favorites.errMess) {
return(
<div className="container">
<div className="row">
<h4>{props.favorites.errMess}</h4>
</div>
</div>
)
}
else if (props.favorites.favorites) {
const favorites = props.favorites.favorites.dishes.map((dish) => {
return (
<div key={dish._id} className="col-12 mt-5">
<RenderMenuItem dish={dish} deleteFavorite={props.deleteFavorite} />
</div>
);
});
return(
<div className="container">
<div className="row">
<Breadcrumb>
<BreadcrumbItem><Link to='/home'>Home</Link></BreadcrumbItem>
<BreadcrumbItem active>My Favorites</BreadcrumbItem>
</Breadcrumb>
<div className="col-12">
<h3>My Favorites</h3>
<hr />
</div>
</div>
<div className="row">
<Media list>
{favorites}
</Media>
</div>
</div>
);
}
else {
return(
<div className="container">
<div className="row">
<h4>You have no favorites</h4>
</div>
</div>
)
}
}
export default Favorites;
The error is obvious
cannot access property "dishes", this.props.favorites.favorites is null
for some reason the favorites object is null and you cannot access the property dishes of the null object
favorite={this.props.favorites.favorites?.dishes?.some((dish) => dish._id === params.dishId)}
import { useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux';
// Actions
import { listProducts } from '../redux/actions/productActions';
const Home = () => {
const dispatch = useDispatch();
const getProduct = useSelector((state) => state.getProduct);
const { products, loading, error } = getProduct;
useEffect(() => {
dispatch(listProducts())
}, [dispatch]);
return (
<div>
{ loading ? <div><Box sx={{ width: '100%' }}>
<LinearProgress />
</Box></div> : error ? <h2>{ error }</h2> : products.map((product) => (
<div className="row center" key={product._id}>
<a href={`/product/${product._id}`}>
<div className="card">
<img className="medium" src={ product.imageUrl } alt="product" />
<div className="card-body">
<h2>{ product.name }</h2>
<Rating rating={product.rating} numReviews={product.numReviews} />
<div className="price">
₦{ product.price.toLocaleString(undefined, { minimumFractionDigits: 2 }) }
</div>
</div>
</div>
</a>
</div>
))}
</div>
)
}
You should remove dispatch from dependency array of useEffect hook