简体   繁体   English

React Native:从多个rest api获取后如何动态渲染多个图像

[英]React Native: How to render muliple images dynamically after fetching from multiple rest api's

I am unable to render multiple images fetched from multiple calls to rest API in react native.我无法渲染从多次调用中获取的多个图像,以在 React Native 中对 rest API 进行调用。

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

The problem is that order details don't have primary image of line_items in rest API.问题是订单详细信息在 rest API 中没有line_items主要图像。 So I need to call each below product detail API for fetching product images for each line_item object via product_id by again calling product details rest API.因此,我需要调用下面的每个产品详细信息 API,以通过再次调用产品详细信息 rest API 来通过product_id获取每个 line_item 对象的产品图像。

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

Till now I have written the logic to call product details for each line_items but I am getting following error with my code.到目前为止,我已经编写了为每个 line_items 调用产品详细信息的逻辑,但是我的代码出现以下错误。 What would be the best way to handle this situation?处理这种情况的最佳方法是什么?

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,

Below is my implementation:下面是我的实现:

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

The way to think about the render() method is that it may run repeatedly.考虑 render() 方法的方式是它可能会重复运行。 In most cases it is re-run every time there's a change that affects its output.在大多数情况下,每次发生影响其输出的更改时都会重新运行它。

The way you have it structured, your render() function calls {this.displayProductSection()} which calls this.getLineItems() which calls this.getProductPrimaryImage(item.product_id) which makes the AJAX request to the WordPress API.你有它的结构的方式,渲染()函数调用{this.displayProductSection()}这就要求this.getLineItems()这就要求this.getProductPrimaryImage(item.product_id)这使得AJAX请求到WordPress的API。

Since render can (and likely will) be run repeatedly, this means your request for the image is being created repeatedly.由于渲染可以(并且可能会)重复运行,这意味着您对图像的请求正在重复创建。

Running an AJAX request is not like displaying an image, where you put the src URL in the tag and the browser loads it once.运行一个AJAX请求并不像显示图像,在那里你把src网址在标签和浏览器加载一次。 HTML is parsed and run once, this is requesting it repeatedly. HTML 被解析并运行一次,这是反复请求它。

A better pattern would be:更好的模式是:

  • In state, track whether the remote data has been requested yet.在 state 中,跟踪是否已请求远程数据。 You could do a status property with init, loading, success, error as possible strings.您可以使用 init、loading、success、error 作为可能的字符串来处理状态属性。
  • In your componentDidMount, request the data.在您的 componentDidMount 中,请求数据。 Change the status from init to loading.将状态从 init 更改为 loading。
  • When the data comes in, change the status from loading to succcess (or error, depending on the result) and store the result in state.当数据进来时,将状态从loading改为success(或error,取决于结果),并将结果存入state。
  • In your render function, do a conditional based on that status property.在您的渲染函数中,根据该状态属性执行条件。 If it is loading, show a loader.如果正在加载,则显示加载器。 If it is success, output the image based on the state you stored above.如果成功,则根据上面存储的状态输出图像。

Sometimes you aren't quite ready to fetch the remote data when the component mounts.有时您还没有准备好在组件安装时获取远程数据。 Maybe it depends on some user input first.也许这首先取决于一些用户输入。 In that case, you could instead hook into componentDidUpdate.在这种情况下,您可以改为挂钩 componentDidUpdate。 Check for your condition there, but since this function call also run repeatedly, also check the status and only request it if it hasn't been requested yet.在那里检查您的条件,但由于函数调用也会重复运行,因此还要检查状态,并且仅在尚未请求时才请求它。

In either case, notice the separation of concerns.无论哪种情况,请注意关注点的分离。 Your render() function is doing one thing -- displaying.您的 render() 函数正在做一件事——显示。 It's not kicking off network requests or triggering side effects.它不会启动网络请求或触发副作用。 Your lifecycle methods (or functions that respond to user input) handle that stuff.您的生命周期方法(或响应用户输入的函数)处理这些东西。

I really appreciated tmdesigned for giving me proper guidance.我真的很感谢 tmdesigned 给了我适当的指导。 I learned the concept of react components again from this link .我从这个链接中再次学习了反应组件的概念。

So I solved my problem by chaining the subsequent fetch requests as a callback in setState which is called inside componentDidMount.因此,我通过将后续获取请求链接为 setState 中的回调来解决我的问题,该回调在 componentDidMount 中调用。 Below is my implementation下面是我的实现

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


声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM