如何使用多个对象映射API [Spree API V2和ReactJS]

时间:2019-03-29 15:37:39

标签: javascript arrays json reactjs spree

我正在建立一个网上商店,前端使用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

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

1 个答案:

答案 0 :(得分:4)

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

您可以将products传递给<ProductsList>组件:

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构造函数中获取产品数组:

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,
    }
  }
}

以下是更新了 的屏幕截图,该屏幕正常工作:

CodeSandbox Screenshot

以及简化的<ProductsList/>组件:

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

要添加图像,这是一个非常简单的任务。您只需要将产品数组与图像结合起来并显示图像即可。检查更新的<ProductsList/>组件。当然,您必须同时将productsimages传递到<ProductsList/>const images = productsData.included;)。检查更新后的CodeSandbox<ProductsList/>组件和屏幕截图。

编辑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是具有typelet image = images.find(image => image.id == id)属性的对象数组。对于产品图像中的每个图像,我们使用48px搜索图像数组,如果找到图像,则使用四个可用尺寸之一或所有可用尺寸(100px,{ {1}},240px600px);我选择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

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

// 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)的更多信息。