#reactjs #react-hooks #e-commerce #context-api
#reactjs #реагирующие крючки #электронная коммерция #реагировать-контекст
Вопрос:
Я создаю типичную электронную коммерцию, по которой вы нажимаете на продукт, а затем получаете конкретную подробную страницу об этом. я хочу увидеть подробную страницу, поэтому я нажимаю на product, но это дает мне неопределенную ошибку, однако, когда я обновляю страницу, она возвращает то, что я хочу, просто проблема, когда я меняю ее с помощью route.
Не удается прочитать свойство ‘image’ undefined , это означает, что product не определен, ProductState.js (Мой контекст)
import React, { useReducer } from "react";
import { productReducer } from "./productReducer";
import axios from "axios";
import {
PRODUCT_DETAILS_FAIL,
PRODUCT_DETAILS_REQUEST,
PRODUCT_DETAILS_SUCCESS,
PRODUCT_LIST_FAIL,
PRODUCT_LIST_REQUEST,
PRODUCT_LIST_SUCCESS,
} from "../constants/productConstants";
export const ProductContext = React.createContext();
const ProductState = ({ children }) => {
const initialState = { products: [], product: { reviews: [] } };
const [state, dispatch] = useReducer(productReducer, initialState);
const listProductDetails = async (id) => {
try {
dispatch({ type: PRODUCT_DETAILS_REQUEST });
const { data } = await axios.get(`/api/products/${id}`);
dispatch({ type: PRODUCT_DETAILS_SUCCESS, payload: data });
} catch (error) {
dispatch({
type: PRODUCT_DETAILS_FAIL,
payload:
error.response amp;amp; error.response.data.message
? error.response.data.message
: error.message,
});
}
};
return (
<ProductContext.Provider
value={{
products: state.products,
error: state.error,
loading: state.loading,
product: state.product,
listProductDetails,
}}
>
{children}
</ProductContext.Provider>
);
};
export default ProductState;
Productscreen.js (Где я хочу подробно ознакомиться с конкретными продуктами)
import React, { useEffect, useContext, useState } from "react";
import { Link } from "react-router-dom";
import {Button, Card, Col, Form, Image,ListGroup, Row,} from "react-bootstrap";
import { ProductContext } from "../productContext/productState";
import Spinnerr from "../components/Spinnerr";
import Message from "../components/Message";
const Productscreen = ({ match }) => {
const context = useContext(ProductContext);
const { listProductDetails, loading, error, product } = context;
const [quantity, setQuantity] = useState(1);
useEffect(() => {
listProductDetails(match.params.id);
}, []);
return (
<>
<Link className="btn btn-light my-3" to="/">
Back
</Link>
{loading ? (
<Spinnerr />
) : error ? (
<Message variant="danger">{error}</Message>
) : (
<Row>
<Col md={6}>
<Image src={product.image} alt={product.name} fluid />
</Col>
<Col md={3}>
<ListGroup variant="flush">
<ListGroup.Item>
<h3>{product.name}</h3>
</ListGroup.Item>
<ListGroup.Item>
<Rating
rating={product.rating}
text={`${product.numReviews} reviews`}
/>
</ListGroup.Item>
<ListGroup.Item>Price: ${product.price}</ListGroup.Item>
<ListGroup.Item>
Description: {product.description}
</ListGroup.Item>
</ListGroup>
</Col>
<Col md={3}>
<Card>
<ListGroup variant="flush">
<ListGroup.Item>
<Row>
<Col>Price:</Col>
<Col>
<strong>${product.price}</strong>
</Col>
</Row>
</ListGroup.Item>
<ListGroup.Item>
<Row>
<Col>Status:</Col>
<Col>
{product.countInStock > 0 ? "In Stock" : "Out of Stock"}
</Col>
</Row>
</ListGroup.Item>
{product.countInStock > 0 amp;amp; (
<ListGroup.Item>
<Row>
<Col>Qty</Col>
<Col>
<Form.Control
as="select"
value={quantity}
onChange={(e) => setQuantity(e.target.value)}
>
{[...Array(product.countInStock).keys()].map((x) => {
return (
<option key={x 1} value={x 1}>
{x 1}
</option>
);
})}
</Form.Control>
</Col>
</Row>
</ListGroup.Item>
)}
<ListGroup.Item>
<Button
className="btn-block"
type="button"
disabled={product.countInStock === 0}
>
Add to Cart
</Button>
</ListGroup.Item>
</ListGroup>
</Card>
</Col>
</Row>
)}
</>
);
};
export default Productscreen;
ProductReducer.js
import {
PRODUCT_DETAILS_FAIL,
PRODUCT_DETAILS_REQUEST,
PRODUCT_DETAILS_SUCCESS,
PRODUCT_LIST_FAIL,
PRODUCT_LIST_REQUEST,
PRODUCT_LIST_SUCCESS,
} from "../constants/productConstants";
export const productReducer = (state, action) => {
switch (action.type) {
case PRODUCT_LIST_REQUEST:
return { loading: true, products: [] };
case PRODUCT_LIST_SUCCESS:
return { loading: false, products: action.payload };
case PRODUCT_LIST_FAIL:
return { loading: false, error: action.payload };
case PRODUCT_DETAILS_REQUEST:
return { loading: true, ...state };
case PRODUCT_DETAILS_SUCCESS:
return { loading: false, product: action.payload };
case PRODUCT_DETAILS_FAIL:
return { loading: false, error: action.payload };
default:
return state;
}
};
ProductRoutes.js
const express = require("express");
const router = express.Router();
const Product = require("../models/ProductModel");
router.get("/", async (req, res) => {
try {
const products = await Product.find({});
res.json(products);
} catch (error) {
console.log(error);
}
});
router.get("/:id", async (req, res) => {
try {
const product = await Product.findById(req.params.id);
if (product) {
res.json(product);
} else {
res.status(404).json({ message: "Product not found" });
}
} catch (error) {
console.log(error);
}
});
module.exports = router;
Комментарии:
1. 1. Для вашего вызова useEffect массив зависимостей должен содержать
match
listProductDetails
параметры и (в противном случае ваш useEffect не будет отозван приmatch.params
обновлении). 2. На вашем месте, если вы не хотите снова изобретать колесо или учиться чему-то самостоятельно, используйте что-то вродеreact-query
, вы удалите примерно половину написанного вами кода, и ваш код будет менее подвержен ошибкам.2. Я их уже делал, но ничего не менял, в любом случае, я решил это окончательно, вы можете увидеть, как я это сделал ниже, и я попробую react-query , это звучит полезно, большое спасибо!
Ответ №1:
После долгих усилий мне удалось это решить,
Мой ProductReducer был установлен неправильно, истинная форма
export const productReducer = (state, action) => {
switch (action.type) {
case PRODUCT_LIST_REQUEST:
return { ...state, loading: true, products: [] };
case PRODUCT_LIST_SUCCESS:
return { ...state, loading: false, products: action.payload };
case PRODUCT_LIST_FAIL:
return { loading: false, error: action.payload };
case PRODUCT_DETAILS_REQUEST:
return { ...state, loading: true };
case PRODUCT_DETAILS_SUCCESS:
return { ...state, loading: false, product: action.payload };
case PRODUCT_DETAILS_FAIL:
return { loading: false, error: action.payload };
default:
return state;
Добавлено «… состояние» для всех успешных случаев, это решаемая проблема, но в Redux (не в hooks) оно работает без необходимости … состояния, что сбивает с толку.