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

Viewed 6954

I'm trying to access data from commerceJS API fetched on a different module and passed down as a prop, the code only breaks when I call a nested product object with this error Uncaught TypeError: Cannot read properties of undefined (reading 'source').

I have used the same API on the storefront and {product.media.source} works fine. I'm trying to make a preview module and when I call the nested object it breaks. Please help.

Here's my code

const Preview = ({ products, onAddToCart }) => {
const [product, setProduct] = useState({});

const params = useParams();



const findProduct = (id) => {
    return products.find((product) => {
        setProduct(product);
        return product.id === id;
    });
};
const productId = params.id;

useEffect(() => {
    findProduct(productId);
});
return (
    <Card sx={{ maxWidth: 345 }} style={{ marginTop: '90px' }}>
        <CardMedia image={product.media.source} title={product.name}></CardMedia>
        <CardContent>
            <Typography gutterBottom variant='h5' component='div'>
                {product.name}
            </Typography>
            <Typography
                variant='body2'
                color='textSecondary'
                dangerouslySetInnerHTML={{ __html: product.description }}
            ></Typography>
        </CardContent>
        <CardActions>
            <IconButton component={Link} variant='outlined' to='/'>
                Go Back
            </IconButton>
            <IconButton aria-label='Add to Cart' onClick={() => onAddToCart(product.id, 1)}>
                <AddShoppingCart />
            </IconButton>
        </CardActions>
    </Card>
);

Here's the product Object product obj

2 Answers

Based on your code above

console.log(products[0]);
console.log(params.id);

const findProduct = (id) => {
    return products.find((product) => {
        setProduct(product);
        return product.id === id;
    });
};

Looks like the product is an array and not an object. I think this is where you might have missed.

Hence, product.media.source throws error as product.media is undefined.

Please check which product you might want to add.

Apparently your product object does not contain media field by default. You need to wait until it is available before using it.

Try something like this:

const Preview = ({ products, onAddToCart }) => {
    const [product, setProduct] = useState();

    const params = useParams();

    console.log(products[0]);
    console.log(params.id);

    const findProduct = useCallback((id) => {
        return products.find((product) => {
            if (product.id === id) { // <-- render optimization
                setProduct(product);
                return true;    
            }
            return false;
        });
    }, [products]);

    const productId = useMemo(() => params.id, [params.id]);

    useEffect(() => {
        findProduct(productId);
    }, [productId, findProduct]);

    const renderCard = useCallback(() => {
        if (!product) return null; // <-- here you can implement your placeholder component
        return (
            <Card sx={{ maxWidth: 345 }} style={{ marginTop: '90px' }}>
                <CardMedia image={product.media.source} title={product.name}/>
                <CardContent>
                    <Typography gutterBottom variant='h5' component='div'>
                        {product.name}
                    </Typography>
                    <Typography
                        variant='body2'
                        color='textSecondary'
                        dangerouslySetInnerHTML={{ __html: product.description }}
                    ></Typography>
                </CardContent>
                <CardActions>
                    <IconButton component={Link} variant='outlined' to='/'>
                        Go Back
                    </IconButton>
                    <IconButton aria-label='Add to Cart' onClick={() => onAddToCart(product.id, 1)}>
                        <AddShoppingCart />
                    </IconButton>
                </CardActions>
            </Card>
        );
    }, [product]);

    return renderCard();
};

Or like this:

const initialState = { // <-- you need to change this as you need
    active: true,
    assets: [/* ... */],
    categories: [/* ... */],
    checkout_url: {
        checkout: 'placeholder://checkout.url',
        display: 'placeholder://display.url',
        /* ... */
    },
    collects: {/*...*/}
    created: 0,
    description: 'placeholder description',
    extra_fields: [/* ... */],
    hsa: {/* ... */},
    id: 'placeholder_id',
    invenotry: {/* ... */},
    is: {/* ... */},
    media: {
        type: 'image',
        source: 'placeholder://image.source',
        /* ... */
    },
    meta: null,
    /* ... */
};

const Preview = ({ products, onAddToCart }) => {
    const [product, setProduct] = useState(initialState);

    const params = useParams();

    console.log(products[0]);
    console.log(params.id);

    const findProduct = useCallback((id) => {
        return products.find((product) => {
            if (product.id === id) { // <-- render optimization
                setProduct(product);
                return true;    
            }
            return false;
        });
    }, [products]);

    const productId = useMemo(() => params.id, [params.id]);

    useEffect(() => {
        findProduct(productId);
    }, [productId, findProduct]);

    return (
        <Card sx={{ maxWidth: 345 }} style={{ marginTop: '90px' }}>
            <CardMedia image={product.media.source} title={product.name}/>
            <CardContent>
                <Typography gutterBottom variant='h5' component='div'>
                    {product.name}
                </Typography>
                <Typography
                    variant='body2'
                    color='textSecondary'
                    dangerouslySetInnerHTML={{ __html: product.description }}
                ></Typography>
            </CardContent>
            <CardActions>
                <IconButton component={Link} variant='outlined' to='/'>
                    Go Back
                </IconButton>
                <IconButton aria-label='Add to Cart' onClick={() => onAddToCart(product.id, 1)}>
                    <AddShoppingCart />
                </IconButton>
            </CardActions>
        </Card>
    );
};
Related