Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ht4v2 #37

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@

## HT4

1. Переписать редьюсеры **review** и **restaurant** на key=>value (аналогично **products**)
1. Переписать редьюсеры **review** и **restaurant**
на key=>value (аналогично **products**)
2. Добавить **users** редьюсер
3. Починить отображение **Review** компонента (взять данные из редьюсеров **review** и **users**)
4. Написать **middleware** для генерации **[uuid](https://github.com/uuidjs/uuid)**
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"redux": "^4.0.5",
"redux-devtools-extension": "^2.13.8",
"reselect": "^4.0.0",
"uuid": "^8.3.2",
"web-vitals": "^0.2.4"
},
"scripts": {
Expand Down
11 changes: 9 additions & 2 deletions src/components/basket/basket.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,17 @@ function Basket({ title = 'Basket', total, orderProducts }) {
</div>
);
}
const mapStateToProps = (state) => {
return {
total: totalSelector(state),
orderProducts: orderProductsSelector(state),
};
};
export default connect(mapStateToProps)(Basket);

export default connect((state) => {
/*export default connect((state) => {
return {
total: totalSelector(state),
orderProducts: orderProductsSelector(state),
};
})(Basket);
})(Basket);*/
27 changes: 23 additions & 4 deletions src/components/restaurant/restaurant.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,41 @@
import React, { useMemo } from 'react';
import React, { useEffect, useMemo } from 'react';
import PropTypes from 'prop-types';
import Menu from '../menu';
import Reviews from '../reviews';
import Banner from '../banner';
import Rate from '../rate';
import Tabs from '../tabs';
import { useDispatch, useSelector } from 'react-redux';
import { setRestaurantActive } from '../../redux/actions';

const Restaurant = ({ restaurant }) => {
const dispatch = useDispatch();
useEffect(() => {
dispatch(setRestaurantActive(restaurant.id));
}, [restaurant.id]);

const { name, menu, reviews } = restaurant;
const allReviews = useSelector((state) => state.reviews);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

все это нужно выносить в селекторы, чтобы компонент занимался только рендером

const filteredReviews = useMemo(() => {
return Object.keys(allReviews).filter((allReviewKey) =>
reviews.includes(allReviewKey)
);
}, [allReviews, reviews]);

const averageRating = useMemo(() => {
const total = reviews.reduce((acc, { rating }) => acc + rating, 0);
const total = filteredReviews
.map((reviewId) => allReviews[reviewId].rating)
.reduce((acc, rating) => acc + rating, 0);

return Math.round(total / reviews.length);
}, [reviews]);
}, [reviews, allReviews]);

const tabs = [
{ title: 'Menu', content: <Menu menu={menu} /> },
{ title: 'Reviews', content: <Reviews reviews={reviews} /> },
{
title: 'Reviews',
content: <Reviews filteredReviews={filteredReviews} />,
},
];

return (
Expand Down
9 changes: 3 additions & 6 deletions src/components/restaurants/restaurants.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import Restaurant from '../restaurant';
import Tabs from '../tabs';
import { allRestaurantsSelector } from '../../redux/selectors';

const Restaurants = ({ restaurants }) => {
const tabs = restaurants.map((restaurant) => ({
Expand All @@ -14,13 +15,9 @@ const Restaurants = ({ restaurants }) => {
};

Restaurants.propTypes = {
restaurants: PropTypes.arrayOf(
PropTypes.shape({
id: PropTypes.string.isRequired,
}).isRequired
).isRequired,
restaurants: PropTypes.arrayOf(PropTypes.string.isRequired).isRequired,
};

export default connect((state) => ({
restaurants: state.restaurants,
restaurants: allRestaurantsSelector(state),
}))(Restaurants);
28 changes: 20 additions & 8 deletions src/components/reviews/review-form/review-form.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,24 @@ import useForm from '../../../hooks/use-form';

import Rate from '../../rate';
import styles from './review-form.module.css';
import { connect } from 'react-redux';
import { connect, useSelector } from 'react-redux';
import Button from '../../button';
import { postReview } from '../../../redux/actions';

const INITIAL_VALUES = { name: '', text: '', rating: 5 };
const INITIAL_VALUES = { yourName: '', text: '', rating: 5 };

const ReviewForm = ({ onSubmit }) => {
const { values, handlers, reset } = useForm(INITIAL_VALUES);

const activeRestaurantId = useSelector(
(state) => state.restaurants.activeRestaurantId
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

это нужно выносить в селекторы

);
const reviewState = useSelector((state) => state.reviews);
const { error, errorMessage } = reviewState;

const handleSubmit = (ev) => {
ev.preventDefault();
onSubmit(values);
onSubmit(values, activeRestaurantId);
reset();
};

Expand All @@ -25,8 +32,9 @@ const ReviewForm = ({ onSubmit }) => {
<input
placeholder="Your name"
className={styles.message}
{...handlers.name}
{...handlers.yourName}
/>
{error && <p className={styles.errorMessage}>{errorMessage}</p>}
</div>
<div className={styles.reviewFormItem}>
<textarea
Expand All @@ -42,7 +50,7 @@ const ReviewForm = ({ onSubmit }) => {
</span>
</div>
<div className={styles.publish}>
<Button primary block>
<Button error={error} primary block>
PUBLISH REVIEW
</Button>
</div>
Expand All @@ -51,6 +59,10 @@ const ReviewForm = ({ onSubmit }) => {
);
};

export default connect(null, () => ({
onSubmit: (values) => console.log(values), // TODO
}))(ReviewForm);
const mapDispatchToProps = (dispatch) => {
return {
onSubmit: (values, activeRestaurantId) =>
dispatch(postReview(values, activeRestaurantId)),
};
};
export default connect(null, mapDispatchToProps)(ReviewForm);
3 changes: 3 additions & 0 deletions src/components/reviews/review-form/review-form.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,6 @@
margin-bottom: 10px;
}
}
.errorMessage {
color: red;
}
47 changes: 32 additions & 15 deletions src/components/reviews/review/review.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,41 @@ import PropTypes from 'prop-types';

import Rate from '../../rate';
import styles from './review.module.css';
import { useSelector } from 'react-redux';

const Review = ({ user, text, rating }) => (
<div className={styles.review} data-id="review">
<div className={styles.content}>
<div>
<h4 className={styles.name} data-id="review-user">
{user}
</h4>
<p className={styles.comment} data-id="review-text">
{text}
</p>
</div>
<div className={styles.rate}>
<Rate value={rating} />
const Review = ({ id }) => {
/*
id: "5909796d-5030-4e36-adec-68b8f9ec2d96"
rating: 5
text: "Not bad"
userId: "a304959a-76c0-4b34-954a-b38dbf310360"
*/

const reviews = useSelector((state) => state.reviews);
const users = useSelector((state) => state.users);
const review = reviews[id];
const { text, rating } = review;
const userId = review.userId;
const user = users[userId].name;

return (
<div className={styles.review} data-id="review">
<div className={styles.content}>
<div>
<h4 className={styles.name} data-id="review-user">
{user}
</h4>
<p className={styles.comment} data-id="review-text">
{text}
</p>
</div>
<div className={styles.rate}>
<Rate value={rating} />
</div>
</div>
</div>
</div>
);
);
};

Review.propTypes = {
user: PropTypes.string,
Expand Down
11 changes: 7 additions & 4 deletions src/components/reviews/reviews.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import React from 'react';
import React, { useMemo } from 'react';
import PropTypes from 'prop-types';
import Review from './review';
import ReviewForm from './review-form';
import styles from './reviews.module.css';
import { connect } from 'react-redux';
import { allReviewsSelector } from '../../redux/selectors';

const Reviews = ({ reviews }) => {
const Reviews = ({ filteredReviews }) => {
return (
<div className={styles.reviews}>
{reviews.map((review) => (
<Review key={review.id} {...review} />
{filteredReviews.map((review) => (
<Review key={review} id={review} />
))}

<ReviewForm />
</div>
);
Expand Down
16 changes: 15 additions & 1 deletion src/redux/actions.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
import { INCREMENT, DECREMENT, REMOVE } from './constants';
import {
INCREMENT,
DECREMENT,
REMOVE,
POST_REVIEW,
SET_ACTIVE_RESTAURANT,
} from './constants';

export const increment = (id) => ({ type: INCREMENT, payload: { id } });
export const decrement = (id) => ({ type: DECREMENT, payload: { id } });
export const remove = (id) => ({ type: REMOVE, payload: { id } });
export const postReview = ({ yourName, text, rating }, activeRestaurantId) => ({
type: POST_REVIEW,
payload: { yourName, text, rating, activeRestaurantId },
});
export const setRestaurantActive = (id) => ({
type: SET_ACTIVE_RESTAURANT,
payload: { id },
});
2 changes: 2 additions & 0 deletions src/redux/constants.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export const INCREMENT = 'INCREMENT';
export const DECREMENT = 'DECREMENT';
export const REMOVE = 'REMOVE';
export const POST_REVIEW = 'POST_REVIEW';
export const SET_ACTIVE_RESTAURANT = 'SET_ACTIVE_RESTAURANT';
15 changes: 15 additions & 0 deletions src/redux/middleware/generateId.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { v4 as uuidv4 } from 'uuid';
import restaurants from '../reducer/restaurants';

export default (store) => (next) => (action) => {
if (action.type === 'POST_REVIEW') {
const newReviewId = uuidv4();
const newUserId = uuidv4();
action.payload.id = newReviewId;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

не стоит мутировать action, лучше создать новый

action.payload.userId = newUserId;
//action.payload.restaurantId = store.getState().restaurants.activeRestaurantId
next(action);
} else {
next(action);
}
};
2 changes: 2 additions & 0 deletions src/redux/reducer/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ import order from './order';
import restaurants from './restaurants';
import products from './products';
import reviews from './reviews';
import users from './users';

export default combineReducers({
order,
restaurants,
products,
reviews,
users,
});
1 change: 1 addition & 0 deletions src/redux/reducer/products.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const defaultProducts = normalizedProducts.reduce(
(acc, product) => ({ ...acc, [product.id]: product }),
{}
);

export default (products = defaultProducts, action) => {
const { type } = action;

Expand Down
29 changes: 27 additions & 2 deletions src/redux/reducer/restaurants.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,34 @@
import { normalizedRestaurants as defaultRestaurants } from '../../fixtures';
import { normalizedRestaurants } from '../../fixtures';
import { POST_REVIEW, SET_ACTIVE_RESTAURANT } from '../constants';

const defaultRestaurants = normalizedRestaurants.reduce(
(acc, restaurant) => ({
...acc,
[restaurant.id]: restaurant,
}),
{}
);

export default (restaurants = defaultRestaurants, action) => {
const { type } = action;
const { type, payload } = action;

switch (type) {
case SET_ACTIVE_RESTAURANT:
return { ...restaurants, activeRestaurantId: payload.id };
Copy link
Owner

@koretskiyav koretskiyav Jan 12, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

теперь знание о какой ресторан активный храниться в двух местах, в этом редьюсере и в компоненте Tabs, я расскажу на занятие какие могут возникнуть с этим проблемы

break;
case POST_REVIEW:
if (!payload.yourName) {
return { ...restaurants };
break;
}
const activeRestaurantId = payload.activeRestaurantId;
const restaurant = restaurants[activeRestaurantId];
restaurant.reviews.push(payload.id);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

мутации в редьюсере недопустимы, на любом уровне вложености

return {
...restaurants,
[payload.activeRestaurantId]: restaurant,
};
break;
default:
return restaurants;
}
Expand Down
32 changes: 30 additions & 2 deletions src/redux/reducer/reviews.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,37 @@
import { normalizedReviews as defaultReviews } from '../../fixtures';
import { normalizedReviews } from '../../fixtures';
import { POST_REVIEW } from '../constants';

const defaultReviews = normalizedReviews.reduce(
(acc, review) => ({ ...acc, [review.id]: review }),
{}
);
defaultReviews.error = true;

export default (reviews = defaultReviews, action) => {
const { type } = action;
const { type, payload } = action;

switch (type) {
case POST_REVIEW:
if (!payload.yourName) {
return {
...reviews,
error: true,
errorMessage: 'name is required',
text: payload.text,
};
}
return {
...reviews,
error: false,
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

лучше для этого сделать на одну вложенность больше, чтобы на одном уровне не было разных типов данных (и ревью по id и дополнительные данные про ошибки), у нас это как раз будет сегодня на занятии

errorMessage: '',
[payload.id]: {
id: payload.id,
rating: payload.rating,
text: payload.text,
userId: payload.userId,
},
};
break;
default:
return reviews;
}
Expand Down
Loading