【问题标题】:React Native: How to render muliple images dynamically after fetching from multiple rest api'sReact Native:如何在从多个休息 api 获取后动态渲染多个图像
【发布时间】:2020-01-25 11:37:20
【问题描述】:

我无法在 react native 中渲染从多次调用 rest API 中获取的多个图像。

对于 Rest API 参考,我使用 woocommerce rest API 来获取订单详细信息。 https://woocommerce.github.io/woocommerce-rest-api-docs/#retrieve-an-order

问题是订单详情在rest API 中没有line_items 的主图像。所以我需要通过product_id再次调用product details rest API来调用下面的每个product detail API来获取每个line_item对象的产品图片。

https://woocommerce.github.io/woocommerce-rest-api-docs/#retrieve-a-product

到目前为止,我已经编写了为每个 line_items 调用产品详细信息的逻辑,但是我的代码出现了以下错误。处理这种情况的最佳方法是什么?

Warning: Cannot update during an existing state transition (such as within `render`). Render methods should be a pure function of props and state.

Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.

Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in %s.%s, the componentWillUnmount method,

下面是我的实现:

render() {
        if (this.state.loading) {
            return (
                <View style={{ flex: 1, justifyContent: "center", alignContent: "center", padding: 20 }}>
                    <ActivityIndicator color='#96588a' size='large' />
                </View>
            )
        }

        return (
            <ScrollView style={{ flex: 1 }}>
                {this.displayOrderDataSection()}
                {this.displayProductSection()}
                {this.displayPaymentSection()}
                {this.displayShippingDetailsSection()}
                {this.displayBillingDetailsSection()}
            </ScrollView>
        );
    }

    getProductPrimaryImage = (productId) => {
        let productData = null;
        this.setState({ imageLoading: true });
        let url = `${base_url}/wp-json/wc/v3/products/${productId}?consumer_key=${c_key}&consumer_secret=${c_secret}`
        console.log(url);
        fetch(url)
            .then((response) => response.json())
            .then((responseJson) => {
                this.setState({
                    imageLoading: false,
                    error: responseJson.code || null,
                });
                productData = responseJson
            })
            .then(() => {
                return productData ?
                    ((Array.isArray(productData.images) && productData.images.length) ?
                        productData.images[0].src : null)
                    : null;

            })
            .catch((error) => {
                this.setState({
                    error,
                    imageLoading: false,
                })
            });
    }

    getLineItems = () => {
        let itemArray = [];
        orderData.line_items.forEach(item => {
            let imgSrc = this.getProductPrimaryImage(item.product_id)
            itemArray.push(
                <View key={item.id} style={{ flex: 1, flexDirection: 'row', backgroundColor: 'white' }}>
                    <View style={{ flex: 1, justifyContent: "center", alignContent: "center" }}>
                        <Image source={imgSrc}
                            style={{ height: 100, width: 100 }} resizeMode='contain' />
                    </View>
                    <View style={{ flex: 2, marginTop: 10, marginBottom: 10, justifyContent: "center" }}>
                        <View style={{ marginLeft: 10 }}>
                            <Text>{item.name}</Text>
                            <Text>SKU: {item.sku}</Text>
                            <Text>Price: {this.getCurrencySymbol()}{item.price.toFixed(2)}</Text>
                            <Text>Oty: {item.quantity}</Text>
                            <View>{this.getTMProductOptions(item.meta_data)}</View>
                        </View>
                    </View>
                </View>
            )
        })
        return itemArray;
    }

    displayProductSection = () => {
        return (
            <View style={styles.section}>
                <Text style={styles.titleText}>Product</Text>
                {this.getLineItems()}
            </View>
        )
    }

【问题讨论】:

  • 我要做的只是移动fetch 并将状态操作更新到componentDidMount 生命周期事件中。这样你就像一个无限循环一样调用。

标签: reactjs rest react-native woocommerce woocommerce-rest-api


【解决方案1】:

对 render() 方法的思考方式是它可能会重复运行。在大多数情况下,每次发生影响其输出的更改时都会重新运行它。

按照您的结构方式,您的 render() 函数调用 {this.displayProductSection()},后者调用 this.getLineItems(),后者调用 this.getProductPrimaryImage(item.product_id),向 WordPress API 发出 AJAX 请求。

由于渲染可以(并且可能会)重复运行,这意味着您对图像的请求正在重复创建。

运行 AJAX 请求不像像显示图像,您将 src URL 放入标签中,浏览器加载一次。 HTML被解析并运行一次,这是重复请求它。

更好的模式是:

  • 在状态下,跟踪远程数据是否已经被请求。您可以使用 init、loading、success、error 作为可能的字符串来创建状态属性。
  • 在您的 componentDidMount 中,请求数据。将状态从初始化更改为加载。
  • 当数据进来时,将状态从加载更改为成功(或错误,取决于结果)并将结果存储在状态中。
  • 在您的渲染函数中,根据该状态属性执行条件。如果正在加载,请显示加载程序。如果成功,根据上面存储的状态输出图片。

有时您还没有准备好在组件挂载时获取远程数据。也许它首先取决于一些用户输入。在这种情况下,您可以改为挂钩到 componentDidUpdate。在那里检查您的情况,但由于 this 函数调用也会重复运行,因此还要检查状态并仅在尚未请求时才请求它。

无论哪种情况,请注意关注点的分离。你的 render() 函数正在做一件事——显示。它不会启动网络请求或触发副作用。您的生命周期方法(或响应用户输入的函数)处理这些内容。

【讨论】:

    【解决方案2】:

    我非常感谢 tmdesign 给予我适当的指导。我又从这个link学到了react组件的概念。

    所以我通过将后续的 fetch 请求链接为 setState 中的回调解决了我的问题,该回调在 componentDidMount 内部调用。下面是我的实现

        componentDidMount() {
            this.focusListener = this.props.navigation.addListener('didFocus', () => {
                this.fetchOrderDetails()
            });
        }
    
        fetchOrderDetails = () => {
            const url = `${base_url}/wp-json/wc/v3/orders/${orderId}?consumer_key=${c_key}&consumer_secret=${c_secret}`;
            this.setState({ loading: true });
            fetch(url).then((response) => response.json())
                .then((responseJson) => {
                    this.setState({
                        orderData: responseJson,
                        error: responseJson.code || null,
                    }, this.fetchOrderStatus())
                }).catch((error) => {
                    this.setState({
                        error,
                        loading: false
                    })
                });
        }
    
        fetchOrderStatus = () => {
            const orderStatusesurl = `${base_url}/wp-json/wc/v3/reports/orders/totals?consumer_key=${c_key}&consumer_secret=${c_secret}`;
            fetch(orderStatusesurl).then(response => response.json())
                .then(responseJson => {
                    let orderStatusMap = new Map();
                    if (Array.isArray(responseJson) && responseJson.length > 0) {
                        if ('slug' in responseJson[0] && 'name' in responseJson[0]) {
                            responseJson.forEach(item => {
                                orderStatusMap.set(item.slug, item.name)
                            })
                        }
                    }
                    this.setState({
                        orderStatusOptions: [...orderStatusMap],
                        orderStatusValue: this.state.orderData.status,
                        loading: false,
                    }, this.fetchOrderProductImages())
                })
        }
    
        fetchOrderProductImages = () => {
            this.state.orderData.line_items.forEach((item, index) => {
                this.fetchProductPrimaryImage(item.product_id, index)
            })
        }
    
        fetchProductPrimaryImage = (productId, index) => {
            this.setState({ imageLoading: true });
            let url = `${base_url}/wp-json/wc/v3/products/${productId}?consumer_key=${c_key}&consumer_secret=${c_secret}`
            fetch(url)
                .then((response) => response.json())
                .then(responseJson => {
                    if ('images' in responseJson && Array.isArray(responseJson.images) && responseJson.images.length) {
                        if ('line_items' in this.state.orderData && Array.isArray(this.state.orderData.line_items) && this.state.orderData.line_items.length) {
                            let modifiedOrderData = this.state.orderData
                            modifiedOrderData.line_items[index].primary_image_src = responseJson.images[0].src
                            this.setState({
                                orderData: modifiedOrderData,
                                imageLoading: false,
                                error: responseJson.code || null,
                            })
                        }
                    } else {
                        this.setState({
                            imageLoading: false,
                            error: responseJson.code || null,
                        });
                    }
                })
                .catch((error) => {
                    this.setState({
                        error,
                        imageLoading: false,
                    })
                });
        }
    
        getLineItems = () => {
            let itemArray = [];
            this.state.orderData.line_items.forEach(item => {
                itemArray.push(
                    <View key={item.id} style={{ flex: 1, flexDirection: 'row', backgroundColor: 'white' }}>
                        <View style={{ flex: 1, justifyContent: "center", alignContent: "center" }}>
                            <Image source={'primary_image_src' in item?{uri: item.primary_image_src}:null}
                                style={{ height: 100, width: 100 }} resizeMode='contain' />
                        </View>
                        <View style={{ flex: 2, marginTop: 10, marginBottom: 10, justifyContent: "center" }}>
                            <View style={{ marginLeft: 10 }}>
                                <Text>{item.name}</Text>
                                <Text>SKU: {item.sku}</Text>
                                <Text>Price: {this.getCurrencySymbol()}{item.price.toFixed(2)}</Text>
                                <Text>Oty: {item.quantity}</Text>
                                <View>{this.getTMProductOptions(item.meta_data)}</View>
                            </View>
                        </View>
                    </View>
                )
            })
            return itemArray;
        }
    
        render() {
            if (this.state.loading) {
                return (
                    <View style={{ flex: 1, justifyContent: "center", alignContent: "center", padding: 20 }}>
                        <ActivityIndicator color='#96588a' size='large' />
                    </View>
                )
            }
    
            return (
                <ScrollView style={{ flex: 1 }}>
                    {this.displayOrderDataSection()}
                    {this.displayProductSection()}
                    {this.displayPaymentSection()}
                    {this.displayShippingDetailsSection()}
                    {this.displayBillingDetailsSection()}
                </ScrollView>
            );
        }
    
    
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2019-11-21
      • 1970-01-01
      • 1970-01-01
      • 2015-12-17
      • 1970-01-01
      • 2018-11-16
      • 2021-02-21
      相关资源
      最近更新 更多