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?
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.find
returns 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>
);
}