Search code examples
javascriptreactjsdjango-rest-frameworkreact-routerreact-router-dom

Why is my product detail not rendering in React when an id is passed?


My project returns an error of undefined is not an object (evaluating 'product.title') while I expect it to return the product's detail.

Please see my fixture below: Fixture.js A

        "id": 1,
        "images": [
            {
                "image": "/media/product-images/43c39863-3c9d-4229-a60f-a57c96fd3278.png"
            }
        ],
        "title": "Asus Zenbook",
        "image": null,
        "is_active": true,
        "bestseller": true,
        "flashsales": false,
        "toppicksforyou": false,
        "monthlysales": false,
        "bestdeals": true,
        "brand": "Asus",
        "sku": "1",
        "price": "1234.00",
        "old_price": "3333.00",
        "quantity": 3,
        "slug": "asus-zenbook",
        "meta_keywords": "Asus",
        "category": 1,
        "brands": []
    },
type here

ProductList.js

import { useState, useEffect, memo, useCallback } from "react";
const React = require('react')



import {
  createBrowserRouter,
  RouterProvider,
  Route,
  Link,
} from "react-router-dom";





function ProductList() {
  const products = useProduct(
    ({ state: { products } }) => products.map(({ id }) => id),
    true
  );
  if (products.length === 0) {
    return (
      <div className="product-list">
        <div className="text-center">There is no product to display</div>
      </div>
    );
  }

  return (
     <div className="flex-wrapper">
      {products.map((product) => (
       <Product
       key={product} 
       id={product} 
     
       
     />
          
       
   

      ))}
    </div>
 
    
 
    
  );
}

export default memo(ProductList);


Product.js

const React = require('react')
import { useState, memo, Fragment, useMemo } from "react";


import useProduct from "./useProduct";

import {
  createBrowserRouter,
  RouterProvider,
  Route,
  Link,
} from "react-router-dom";



function Product({ id, }) {
  const { product, } = useProduct(
    ({
      state: { products },

    }) => ({
      product: products.find((product) => product.id === id),
    }),
    true
  );
 
  return (
    <div className='col-3'>
      <div className='card'>
      <Link to={`/products/${product.id}`}>

        <img
        
          src={`http://127.0.0.1:8000/api/products${product.image}`}
          alt={product.quantity}
          className='card-img-top'
        />
      
        <div className='card-body'>
          <h3 className='card-title'>{product.title}</h3>
          <h3 className='card-title'>{product.price}</h3>
        </div>
           </Link>
      </div>
     
    </div>
 
  );
}




export default memo(Product);

Detail.js

import React from 'react';

import { useState, useEffect } from 'react';

import { useParams } from 'react-router-dom';
import useProduct from './useProduct'



function Detail() {
  const {id} = useParams()
  const { product } = useProduct(
    ({
      state: { products },

    })=> ({
      product: products.find((product) => product.id === id),
    }),
    true
  );   
  console.log(id)
  return (
      <div>
          <h1>{product.title}</h1>
          <p>Price: ${product.price}</p>
          <p>{product.overview}</p>
      </div>
  )
}
export default Detail;

The id params are rendering correctly when I change the id Fixture.js A above to: (notice the double quotes "")

   {
        id: "1",
        ......

  
    },

I get product detail results as expected. The issue is that the Django REST API only returns results with id as shown in Fixture A

What could be the issue? Is it an issue with my DRF?


Solution

  • The id in the data is a number type, e.g. "id": 1, but the route path params are always a string type, e.g. const { id } = useParams(); id will be a string. The issue is that you are using strict equality, i.e. ===, which compares the types and if the two operands are different types they can never be equal. Because no element in the products array passes the predicate condition array.findreturns undefined. You can't access title of undefined and an error is thrown. This is why the UI code works when you changed the id type in the data to a string, i.e. id: "1", the types matched.

    Test

    console.log('1 === "1"', 1 === "1"); // false
    console.log("1 === 1", 1 === 1); // true

    You can either use string-type id values in the JSON data

    id: "......"
    

    Or use loose equality checks, e.g. == which will attempt a type coercion

    console.log('1 == "1"', 1 == "1"); // true
    console.log("1 == 1", 1 == 1); // true

    Or use a type-safe comparison by casting all values to same type. I suggest using string types as this covers all strings, including number-like string values.

    console.log('String(1) === "1"', String(1) === "1"); // true
    console.log('String("1") === "1"', String("1") === "1"); // true

    Also, because Array.prototype.find potentially returns undefined you should safe-guard the UI to prevent accidental undefined accesses. In other words, check first if there's actually a defined product value.

    function Detail() {
      const { id } = useParams();
    
      const product = useProduct(
        ({ state })=> state.products.find((product) => String(product.id) === id),
        true
      );
    
      if (!product) {
        return <div>No matching product found</div>;
      }
      
      return (
        <div>
          <h1>{product.title}</h1>
          <p>Price: ${product.price}</p>
          <p>{product.overview}</p>
        </div>
      );
    }