Ошибка неперехваченного типа: Не удается прочитать свойства неопределенного (чтение «источника»)

#reactjs #react-hooks

Вопрос:

Я пытаюсь получить доступ к данным из API commerceJS, извлеченным из другого модуля и переданным в качестве поддержки, код прерывается только тогда, когда я вызываю вложенный объект продукта с этой ошибкой Uncaught TypeError: Cannot read properties of undefined (reading 'source') .

Я использовал тот же API на витрине магазина и {product.media.source} отлично работает. Я пытаюсь создать модуль предварительного просмотра, и когда я вызываю вложенный объект, он ломается. Пожалуйста, помогите.

Вот мой код

 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>
);
 

Вот объект продукта
объект продукта

Комментарии:

1. Очевидно, ваш product объект не содержит media поля. Можете ли вы предоставить содержимое product объекта?

2. Готово, вы должны найти там изображение в формате png.

Ответ №1:

По-видимому, ваш product объект по умолчанию не содержит media поля. Вам нужно подождать, пока он будет доступен, прежде чем использовать его.

Попробуйте что-нибудь вроде этого:

 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();
};
 

Или вот так:

 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>
    );
};
 

Комментарии:

1. Это работает, это работает! Спасибо!! :D, Загрузка занимает некоторое время, можете ли вы сказать, почему?

2. @CaramelIfy время, необходимое для загрузки, зависит от внутренней логики используемых крючков, а также логики родительских компонентов. Скорее всего, вы где-то делаете запрос api, это требует времени

3. @CaramelIfy даже если бы время загрузки было чрезвычайно коротким, скорее всего, вы все равно получили бы ту же ошибку, потому что в своем коде вы изначально пытались получить доступ к полю undefined объекта

4. Что ж, я думаю, в этом есть смысл @wowandy еще раз спасибо!

5. @CaramelIfy добро пожаловать 🙂

Ответ №2:

На основе вашего кода выше

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

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

 

Похоже, что продукт представляет собой массив, а не объект. Я думаю, что это то, что вы, возможно, пропустили.

Следовательно, product.media.source выдает ошибку, поскольку product.media не определен.

Пожалуйста, проверьте, какой продукт вы, возможно, захотите добавить.