skip to Main Content

I am unable to render multiple images fetched from multiple calls to rest API in react native.

For Rest API reference, I am using woocommerce rest API for getting order details.
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. 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.

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

2

Answers


  1. Chosen as BEST ANSWER

    I really appreciated tmdesigned for giving me proper guidance. 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. 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>
            );
        }
    
    
    

  2. The way to think about the render() method is that it may run repeatedly. 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.

    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. HTML is parsed and run once, this is requesting it repeatedly.

    A better pattern would be:

    • In state, track whether the remote data has been requested yet. You could do a status property with init, loading, success, error as possible strings.
    • In your componentDidMount, request the data. Change the status from init to 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.
    • 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. 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. It’s not kicking off network requests or triggering side effects. Your lifecycle methods (or functions that respond to user input) handle that stuff.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search