Home > Software engineering >  Products aren't displaying, showing products undefined
Products aren't displaying, showing products undefined

Time:11-15

Integrated redux now products aren't showing anymore. I tried adding a condition like "{products && products.map((product) => .."). A blank page disappears but still no products. The error disappears as well though. In the redux devTools I get:

{
  type: 'PRODUCT_LIST_SUCCESS',
  payload: [
    {
      _id: 1,
      name: 'IPhone 11 Pro 256GB memory',
      image: '/images/phone.jpg',
      brand: 'Apple',
      category: 'Electronics',
      description: 'Bluetooth technology lets you connect it with compatible devices wirelessly High-Quality AAC audio offers immense experience Built-in microphone allows you take calls while working',
      rating: '4.00',
      numReviews: 8,
      price: '599.99',
      countInStock: 7,
      created: '2022-11-14T18:29:17.417487Z',
      user: 1
    },
  ]
}

store.js:

import {
  legacy_createStore as createStore,
  combineReducers,
  applyMiddleware,
} from "redux";
//import { configureStore } from "@reduxjs/toolkit";
import thunk from "redux-thunk";
import { composeWithDevTools } from "redux-devtools-extension";
import {
  productListReducers,
  productDetailsReducers,
} from "./reducers/ProductReducers";

const reducer = combineReducers({
  productList: productListReducers,
  productDetails: productDetailsReducers,
});

const initialState = {};
const middleware = [thunk];
const store = createStore(
  reducer,
  initialState,
  composeWithDevTools(applyMiddleware(...middleware))
);

export default store;

app.js:

import "./App.css";
import "./index.css";
import { Container } from "react-bootstrap";
import { BrowserRouter as Router, Route, Routes } from "react-router-dom";
import Footer from "./components/Footer";
import Header from "./components/Header";
import HomeScreen from "./components/screens/HomeScreen";
import ProductScreen from "./components/screens/ProductScreen";

function App() {
  return (
    <Router>
      <Header />
      <main className="my-3">
        <Container>
          <Routes>
            <Route path="/" element={<HomeScreen />} />
            <Route path="/product/:id" element={<ProductScreen />} />
          </Routes>
        </Container>
      </main>
      <Footer />
    </Router>
  );
}

export default App;

HomeScreen.js:

import React, { useState, useEffect } from "react";
//import products from "../../products";
import { Row, Col } from "react-bootstrap";
import Product from "../Product";
import { useDispatch, useSelector } from "react-redux";
import { listProducts } from "../../actions/ProductAction";
import Message from "../Message";
import Loader from "../Loader";

function HomeScreen() {
  const dispatch = useDispatch();
  const productList = useSelector((state) => state.productList);
  const { error, loading, products } = productList;
  useEffect(() => {
    dispatch(listProducts());
  }, [dispatch]);

  return (
    <div>
      <h1 className="text-center">Latest Products</h1>
      {loading ? (
        <Loader />
      ) : error ? (
        <Message variant="danger">{error}</Message>
      ) : (
        <Row>
          {products &&
            products.map((product) => (
              <Col key={product._id} sm={12} md={6} lg={4} xl={3}>
                {/* <h3>{product.name}</h3> */}
                <Product product={product} />
              </Col>
            ))}
        </Row>
      )}
    </div>
  );
}

export default HomeScreen;

ProductScreen.js:

import React, { useState, useEffect } from "react";
import { Link, useParams } from "react-router-dom";
import { Row, Col, Image, ListGroup, Button, Card } from "react-bootstrap";
//import products from "../../products";
import Rating from "../Rating";
import { useDispatch, useSelector } from "react-redux";
import Message from "../Message";
import Loader from "../Loader";
import { listProductDetails } from "../../actions/ProductAction";
import { productDetailsReducers } from "../../reducers/ProductReducers";

function ProductScreen() {
  const [quantity, setQuantity] = useState(1);
  const { id } = useParams();
  const dispatch = useDispatch();
  const productDetails = useSelector((state) => state.productDetails);
  const { loading, error, product } = productDetails;

  useEffect(() => {
    async function fetchProduct() {
      if (id) {
        dispatch(listProductDetails(id));
      }
    }
    fetchProduct();
  }, [dispatch, id]);
  return (
    <div>
      <Link to="/" className="btn btn-dark my-3">
        Go Back
      </Link>
      {loading ? (
        <Loader />
      ) : error ? (
        <Message variant="danger">{error}</Message>
      ) : (
        <Row>
          <Col md={6}>
            <Image
              className="imgCustom"
              src={product.image}
              alt={product.name}
            ></Image>
          </Col>
          <Col md={3}>
            <ListGroup variant="flush">
              <ListGroup.Item>
                <h3>{product.name}</h3>
              </ListGroup.Item>
              <ListGroup.Item>
                <Rating
                  value={product.rating}
                  text={`${product.numReviews} reviews`}
                  color={"f8e825"}
                />
              </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>

                <ListGroup.Item>
                  <Button
                    className="btn-block"
                    disabled={product.countInStock === 0}
                    type="button"
                  >
                    Add to Cart
                  </Button>
                </ListGroup.Item>
              </ListGroup>
            </Card>
          </Col>
        </Row>
      )}
    </div>
  );
}

export default ProductScreen;

ProductAction.js:

import axios from "axios";
import {
  PRODUCT_LIST_REQUEST,
  PRODUCT_LIST_SUCCESS,
  PRODUCT_LIST_FAIL,
  PRODUCT_DETAILS_REQUEST,
  PRODUCT_DETAILS_SUCCESS,
  PRODUCT_DETAILS_FAIL,
} from "../constants/ProductConstants";

export const listProducts = () => async (dispatch) => {
  try {
    dispatch({ type: PRODUCT_LIST_REQUEST });

    const { data } = await axios.get("/api/products/");

    dispatch({
      type: PRODUCT_LIST_SUCCESS,
      payload: data,
    });
  } catch (error) {
    dispatch({
      type: PRODUCT_LIST_FAIL,
      payload:
        error.response && error.response.data.detail
          ? error.response.data.detail
          : error.message,
    });
  }
};

export const listProductDetails = (id) => async (dispatch) => {
  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 && error.response.data.message
          ? error.response.data.message
          : error.message,
    });
  }
};

ProductReducers.js:

import {
  PRODUCT_LIST_REQUEST,
  PRODUCT_LIST_SUCCESS,
  PRODUCT_LIST_FAIL,
  PRODUCT_DETAILS_REQUEST,
  PRODUCT_DETAILS_SUCCESS,
  PRODUCT_DETAILS_FAIL,
} from "../constants/ProductConstants";

export const productListReducers = (state = { products: [] }, action) => {
  switch (action.type) {
    case PRODUCT_LIST_REQUEST:
      return { loading: true, product: [] };
    case PRODUCT_LIST_SUCCESS:
      return { loading: false, product: [action.payload] };
    case PRODUCT_LIST_FAIL:
      return { loading: true, product: [action.payload] };

    default:
      return state;
  }
};

export const productDetailsReducers = (state = {product:{ reviews: [] }}, action) => {
  switch (action.type) {
    case PRODUCT_DETAILS_REQUEST:
      return { loading: true, ...state };
    case PRODUCT_DETAILS_SUCCESS:
      return { loading: false, product: [action.payload] };
    case PRODUCT_DETAILS_FAIL:
      return { loading: true, product: [action.payload] };

    default:
      return state;
  }
};

CodePudding user response:

Issue

It appears the HomeScreen component is selecting out a products property that doesn't exist in the redux store state slice after the state is updated at least once.

HomeScreen

function HomeScreen() {
  const dispatch = useDispatch();
  const productList = useSelector((state) => state.productList);
  const { error, loading, products } = productList; // <-- products

Redux store's combined reducers

const reducer = combineReducers({
  productList: productListReducers,
  productDetails: productDetailsReducers,
});

productListReducers

None of the reducer cases here maintain the products array state invariant, they each change/return a product property.

export const productListReducers = (state = { products: [] }, action) => {
  switch (action.type) {
    case PRODUCT_LIST_REQUEST:
      return { loading: true, product: [] };
    case PRODUCT_LIST_SUCCESS:
      return {
        loading: false,
        product: [action.payload] // <-- product, not products
      };
    case PRODUCT_LIST_FAIL:
      return { loading: true, product: [action.payload] };

    default:
      return state;
  }
};

Additionally the PRODUCT_LIST_SUCCESS action appears to return a payload that is a products array.

{
  type: 'PRODUCT_LIST_SUCCESS',
  payload: [
    {
      _id: 1,
      name: 'IPhone 11 Pro 256GB memory',
      image: '/images/phone.jpg',
      brand: 'Apple',
      category: 'Electronics',
      description: 'Bluetooth technology lets you connect it with compatible devices wirelessly High-Quality AAC audio offers immense experience Built-in microphone allows you take calls while working',
      rating: '4.00',
      numReviews: 8,
      price: '599.99',
      countInStock: 7,
      created: '2022-11-14T18:29:17.417487Z',
      user: 1
    },
  ]
}

return { loading: false, product: [action.payload] }; would result in nesting an array within an array, which is likely not what was intended.

Solution

Maintain the products property state invariant and set the products state to be the value of the action.payload. You very also likely want to clear the products array and set some error state in the case of failure.

export const productListReducers = (state = { products: [] }, 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,        // <-- clear loading state
        products: [],          // <-- empty products array
        error: action.payload, // <-- set error state
      };

    default:
      return state;
  }
};
  • Related