【问题标题】:How to map API with multiple objects [Spree API V2 & ReactJS]如何使用多个对象映射 API [Spree API V2 & ReactJS]
【发布时间】:2019-08-20 14:22:01
【问题描述】:

我正在建立一个网上商店,前端使用 ReactJS,后端使用 Spree (Ruby)。

Spree 提供API 解决方案,将前端和后端相互连接。

我正在尝试显示带有产品图片的产品,但 Spree 的 API 是以特定方式设置的,即产品图片和产品不在同一个对象中。

API 响应是:

 {
    (holds products)data: [],
    (Holds product images)included:[],
 }

我的目标是创建一个ul,显示产品信息和产品图片。

我尝试映射my API link

           this.state.arrays.map((product) => 
              product.data
            )

响应数据对象,但我不能例如做product.data.name,因为它返回一个undefined 响应

日志中的数据响应

ProductsList.js:28 PL 
[undefined]
Index.js:42 productsData 
{}
ProductsList.js:28 PL 
[Array(5)]
0: Array(5)
0: {id: "5", type: "image", attributes: {…}}
1: {id: "4", type: "image", attributes: {…}}
2: {id: "1", type: "image", attributes: {…}}
3: {id: "3", type: "image", attributes: {…}}
4: {id: "2", type: "image", attributes: {…}}
length: 5
__proto__: Array(0)
length: 1
__proto__: Array(0)

产品索引页面

import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import PropTypes from "prop-types";
import ProductsList from "./products/ProductsList";
import axios from 'axios';



const REACT_VERSION = React.version;
const include = '?include=images';
const API = 'https://stern-telecom-react-salman15.c9users.io/api/v2/storefront/products' + include;

const styles = {
  card: {
    maxWidth: 345,
  },
  media: {
    height: 140,
  },
};

class Index extends React.Component {
    constructor(props){
        super(props);
        this.state = {
            products: [],
            productsData: {},
            isLoading: false,
            error: null,
    };
  }
  componentDidMount() {
    this.setState({ isLoading: true });
    axios.get(API)
      .then(result => this.setState({
        products: result.data.data,
        productsData: result.data,
        isLoading: false,
      }))
      .catch(error => this.setState({
        error,
        isLoading: false
      }));
      // console.log(
      //   'productsData', 
      //   this.state.productsData

      //   )
  }
  render() {
    const { products, productsData,isLoading, error } = this.state;

    if (error) {
      return <p>{error.message}</p>;
    }
     if (isLoading) {
      return <p>Loading ...</p>;
    }
    return (
      <React.Fragment>
          <h1>React version: {REACT_VERSION}</h1>
          <ProductsList products={this.state.productsData}/>
      </React.Fragment>
    );
  }
}

ProductsList.propTypes = {
  greeting: PropTypes.string
};

export default Index

产品列表页面

import React from "react"
import PropTypes from "prop-types"

import { withStyles } from '@material-ui/core/styles';
import Card from '@material-ui/core/Card';
import CardActionArea from '@material-ui/core/CardActionArea';
import CardActions from '@material-ui/core/CardActions';
import CardContent from '@material-ui/core/CardContent';
import CardMedia from '@material-ui/core/CardMedia';
import Button from '@material-ui/core/Button';
import Typography from '@material-ui/core/Typography';

const url = "https://stern-telecom-react-salman15.c9users.io"

class ProductsList extends React.Component {
  constructor(props) {
    super(props);
    const { products } = this.props;
    const arrays = Object.values( {products} );
    this.state = {
      products,
      arrays
    };
  }
  render () {
    return (
      <React.Fragment>
        <ul>
          <p>Shop Products</p>
          {
          // console.log(
          //   'PL',
            // this.state.arrays.map((product) => 
            //   product.data
            // )
          // )
          this.state.arrays.map(product =>
            <li key={product.objectID}>
            <Card>
                  <CardActionArea>
                    <CardMedia
                      image= {url + ''}
                      title={product.data.attributes.name}
                    />
                    <CardContent>
                      <Typography gutterBottom variant="h5" component="h2">
                       {product.data.attributes.name}
                      </Typography>
                      <Typography component="p">
                        {product.data.attributes.description}
                      </Typography>
                    </CardContent>
                  </CardActionArea>
                  <CardActions>
                    <Button size="small" color="primary">
                     {product.data.attributes.display_price} 
                    </Button>
                    <Button size="small" color="primary">
                      add to cart
                    </Button>
                  </CardActions>
                </Card>
            </li>
            )
          }
        </ul>
      </React.Fragment>
    );
  }
}

ProductsList.propTypes = {
  greeting: PropTypes.string
};
export default ProductsList

我期望得到的结果是产品信息和图像

【问题讨论】:

  • 什么是this.state.arrays
  • 嗨,@bUff23 我​​将它添加到我的代码中。 AP Object get 在 productList 页面上传递并变成一个称为数组的变量。我把它变成了一个状态组件
  • 你能发布一个你从 APi 收到的 json 数据的样本吗?如果没有这些信息,很难看出您面临什么问题
  • @dubes 我已经添加了我收到的数据

标签: javascript arrays json reactjs spree


【解决方案1】:

获取 json 数据后的操作是错误的。返回的结果是一个带有 data 属性的 json 对象,这是您要传递并获取产品的数组。

您可以将products 传递给&lt;ProductsList&gt; 组件:

const { products, images, isLoading, error } = this.state;
...
<ProductsList products={products} images={images}/>

然后直接使用:

class ProductsList extends React.Component {
  constructor(props) {
    super(props);
    const { products, images } = this.props;
    this.state = {
      products,
      images
    };
    ...
  }
  ...
}

或者使用props.products.data直接获取ProductsList构造函数里面的products数组:

class ProductsList extends React.Component {
  constructor(props) {
    super(props);
    const products = this.props.products.data;
    const images = this.props.products.included;
    ...
  }
  ...
}

没有必要使用const arrays = Object.values({ products });,因为您已经有了一个包含产品的数组:

...
products: result.data.data,   // products is an array with products
images: result.data.included, // images is an array with all posible images
productsData: result.data,    // productsData.data is an array with products
...

另外,产品对象不包含任何名为data的属性:

<Typography gutterBottom variant="h5" component="h2">
  {product.data.attributes.name}
</Typography>
<Typography component="p">
  {product.data.attributes.description}
</Typography>

你必须像这样直接访问它的属性:

<Typography gutterBottom variant="h5" component="h2">
  {product.attributes.name}
</Typography>
<Typography component="p">
  {product.attributes.description}
</Typography>

编辑

这是一个CodeSandbox project,您的代码已简化,无需调用 Axios 请求(因为it's restrticted),而是将数据保存在 JSON 文件中。您还应该将isLoading 初始化为true,或者使Index 组件在它有一些数据之前不呈现:

class Index extends React.Component {
  constructor(props){
    super(props);
    this.state = {
      ...
      isLoading: true,
    }
  }
}

这是一个更新的屏幕截图,它正在运行:

还有简化的&lt;ProductsList/&gt; 组件:

import React from "react";

const url = "https://stern-telecom-react-salman15.c9users.io";

class ProductsList extends React.Component {
  constructor(props) {
    super(props);

    const { products, images } = this.props;
    //const arrays = Object.values( {products} );
    this.state = {
      products,
      images
      //arrays
    };
  }
  render() {
    const { products, images } = this.state;
    return (
      <React.Fragment>
        <p>Shop Products</p>
        {console.log("PL", products, images)
        // this.state.arrays.map(product =>
        //   <li key={product.objectID}>

        //   </li>
        //   )
        }
        <ul>
          {products.map(product => (
            <li key={product.key}>
              <h4>{product.attributes.name}</h4>
              <p>Description: {product.attributes.description}</p>
              <p>Price: {product.attributes.display_price} </p>
              <p>Images:</p>
              <div>
                {product.relationships.images.data.map(({ id }) => {
                  let image = images.find(image => image.id == id);
                  return image ? (
                    <img src={`${url}/${image.attributes.styles[1].url}`}/>
                  ) : null;
                })}
              </div>
            </li>
          ))}
        </ul>
      </React.Fragment>
    );
  }
}

export default ProductsList;

编辑 2

要添加图像,这是一项非常简单的任务。您只需要将产品数组与图像结合起来并显示图像。检查更新的&lt;ProductsList/&gt; 组件。当然,您必须将productsimages 都传递给&lt;ProductsList/&gt; (const images = productsData.included;)。查看更新后的CodeSandbox&lt;ProductsList/&gt; 组件和截图。

编辑 3

关于图像;每个图像都有一个styles 属性,它是一个不同大小的数组:

"included": [
{
  "id": "5",
  "type": "image",
  "attributes": {
    "viewable_type": "Spree::Variant",
    "viewable_id": 4,
    "styles": [
      {
        "url": "...",
        "width": "48",
        "height": "48"
      },
      {
        "url": "...",
        "width": "100",
        "height": "100"
      },
      {
        "url": "...",
        "width": "240",
        "height": "240"
      },
      {
        "url": "...",
        "width": "600",
        "height": "600"
      }
    ]
  }
}
...
]

为了将图像映射到每个产品,我们必须使用product.relationships.images.data 映射存储在每个产品中的所有图像,id 是具有idtype 属性的对象数组。对于产品图片中的每张图片,我们使用let image = images.find(image =&gt; image.id == id) 搜索图片数组,如果我们找到一张图片,我们会使用四种可用尺寸中的一种,或者可能使用所有可用尺寸(48px100px、@ 987654359@, 600px);我选择image.attributes.styles[1].url,所以我显示可用图像尺寸的第二个元素,即100px 尺寸图像:

product.relationships.images.data.map(({ id }) => {
  let image = images.find(image => image.id == id);
  return image ? (
    <img src={`${url}/${image.attributes.styles[1].url}`}/>
  ) : null;
})

编辑 4

如果您需要为每个产品获取一张图片,那么您可以使用一个函数来检查图片是否存在,然后从图片数组中获取图片:

// Here we're using an inline function to get the product image
// You can also create a normal class function and use that instead

{product.relationships.images.data.length > 0 &&
  (() => {
    // { id } is the destructure of product.relationships.images.data[0]
    // which means it extract the property id to a stand alone variable
    const { id } = product.relationships.images.data[0];
    const image = images.find(image => image.id == id);
    return image ? (
      <img src={`${url}/${image.attributes.styles[1].url}`} />
    ) : null;
  })()
}

这是一个内联函数,可以隔离其内容并立即执行:

(() => { ... })()

您可以阅读有关Destructuring assignment ({ id } = object) 的更多信息。

【讨论】:

  • 感谢您的回复 Christos,我正在测试 productsData 一个对象 { }products 作为数组 [ ] 将 productsData 作为对象传递给产品列表并将其更改为在记录product.data 时,数组似乎使数据可读,它确实显示了多个数组的数据馈送。但是当我尝试映射一个数组时,响应是未定义的
  • @Salman 如果您从头到尾仔细阅读我的回答,您会发现自己做得不对。您不必使用Object.values 转换任何内容; JSON 数据已经有一个包含产品的数组 (data.data)。您登录ProductsList.js:28 PL [Array(5)] 实际上是图像 included 成员而不是拥有产品的data: [...] 的数据。
  • 感谢编辑,唯一的问题是我不仅需要data 数组,还需要包含的图像。创建包含图像的产品列表。现在数组列表没有图像,所以我必须合并来自datainclude 的数据
  • @Salman 这真的很简单,只是过滤问题。检查我的编辑和代码,看看你会怎么做。
  • 嗨,克里斯,您上次的编辑工作完美,谢谢!您能否再解释一下您在查找图像方面所做的工作?
猜你喜欢
  • 1970-01-01
  • 2022-01-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-07-30
  • 2021-09-16
  • 1970-01-01
  • 2022-01-01
相关资源
最近更新 更多