Search code examples
javascriptreactjsreduxreact-reduxreact-router

Uncaught TypeError: Cannot read properties of undefined (reading 'map') React


So im following this tutorial and im struggling with the following step. Redux was introduced and it not working. I googled the error that I found in the developer Tool and I still couldn't fix the error by trying many proposed solutions. The error is in HomeScreen in line 25 (products.map) My code:

HomeScreen.js:

import React, {useState, useEffect} from 'react'
import {Row, Col} from 'react-bootstrap'
import Product from '../components/Product'
import { useDispatch, useSelector} from 'react-redux'
import {listProducts} from '../actions/productActions'


function HomeScreen() {

    const dispatch = useDispatch()
    const productList = useSelector(state => state.productList)
    const {error, loading, products} = productList

    useEffect(() =>{
        dispatch(listProducts())
    },[dispatch])
    
  return (
    
        <div>
            <h1>Latest Products</h1>
            {loading ? <h2>Loading..</h2>
                : error ? <h3>{error}</h3>
                :
                <Row>
                {products.map(product => (
                    <Col key={product._id} sm={8} md={6} lg={4} xl={2}>
                        <Product product={product} />
                    </Col>
                ))}
                </Row>
            }
            
        </div>
    
  )
}

store.js:

import { legacy_createStore as createStore, combineReducers, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
import { composeWithDevTools } from 'redux-devtools-extension'
import {productListReducer} from './reducers/productReducers'


const reducer = combineReducers({
    productList: productListReducer,
})
const initialState = {}
const middleware = [thunk]
const store = createStore(reducer, initialState, 
    composeWithDevTools(applyMiddleware(...middleware)))

export default store

productActions.js:

import axios from 'axios'
import {PRODUCT_LIST_REQUEST,PRODUCT_LIST_SUCCESS,PRODUCT_LIST_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.message
            ? error.response.data.message
            : error.message, 
        })
    }
}

productReducers.js

import {
    PRODUCT_LIST_REQUEST,
    PRODUCT_LIST_SUCCESS,
    PRODUCT_LIST_FAIL
} from '../constants/productConstants'


export const productListReducer = (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:false, error: action.payload}
        
        default:
            return state
    }
}

Solution

  • You should consistently name the state. The state is initially named products, but all the case reducers replace it with product. Don't forget to also always shallow copy the previous state object into the next state object.

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