Implementation problems while using React's Promises

85 views Asked by At

I'm working on an assignment where I need to implement an order management system. The problem is that I need to make a GET call to get all the orders, and for each order I need to make another GET call so I can get the Items of this order. My question is how can I make those calls and create some data structure of orders and items before rendering everything. I tried using async/await but I couldn't manage to create this data structure of orders and their related items before everything rendered.

For now I only have the orders GET call handled

async componentDidMount() {
        const orders = await api.getOrders()
        this.setState({
            orders
        });
    }

I also created a function for the GET calls of the items which returns a Promise<Item[]>

createItemsList = (order: Order) => {
        let a = order.items.map((item) => {
            return api.getItem(item.id);
        });
        return Promise.all(a);
    };

Any suggestion for a way to combine those two? Thanks in advance!

*** Editing *** This is the part of the code where I render the orders

{filteredOrders.map((order) => (
                    <div className={'orderCard'}>
                        <div className={'generalData'}>
                            <h6>{order.id}</h6>
                            <h4>{order.customer.name}</h4>
                            <h5>Order Placed: {new Date(order.createdDate).toLocaleDateString()},
                            At: {new Date(order.createdDate).toLocaleTimeString()}</h5>
                        </div>
                        <Fulfilment order={order}/>
                        <div className={'paymentData'}>
                            <h4>{order.price.formattedTotalPrice}</h4>
                            <img src={App.getAssetByStatus(order.billingInfo.status)}/>
                        </div>
                        <ItemsList subItemsList={order.items} api={api}/>
                    </div>
                ))}

The component ItemsList is where I render the Items of a specific order, and order.items is not the items itself but an array of items ID and quantities which I get with each order

3

There are 3 answers

0
Emiel Zuurbier On BEST ANSWER

Create a separate method outside that gets the data you need.

First get the orders from the API. Then loop through each order and call the api again for each item. Await the Promise.all to wait for each item in the order to finish, then concatenate the result to an array where you store all the fetched items. Then after the loop is finished return the results array.

In your componentDidMount call this method and update the state based on the result that the promised returned.

state = {
    orders: []
}

async getOrderItems() {
    let orderItems = [];
    const orders = await api.getOrders()
    for (const order of orders) {
        const items = await Promise.all(
            order.items.map((item) => api.getItem(item.id))
        )
        orderItems = [...orderItems, ...items]
    }
    return orderItems
}

componentDidMount() {
    this.getOrderItems().then(orders => {
        this.setState({
            orders
        })
    })
}

render() {
    if (this.state.orders.length === 0) {
        return null
    }

    return (
        {this.state.orders.map(order => (
            // Render content.
        )}
    )
}
1
Fahad Shinwari On

You can try this solution.I was stuck in the same issue a few days ago.So what it does is that setState renders after createItemsList function is run

async componentDidMount() {
            const orders = await api.getOrders()
            this.setState({
                orders
            }),
        
            () => this.createItemsList()
    }
1
Karl Lopez On

I suggest you move the data retrieval into each component.

Check the sandbox here

import React, { PureComponent } from "react";


const fakeOrderItems = {
  1: [
    {
      id: 1,
      name: "Ramen",
      qty: 1
    },
    {
      id: 1,
      name: "Beer",
      qty: 1
    }
  ],
  2: [
    {
      id: 1,
      name: "Steak",
      qty: 1
    },
    {
      id: 2,
      name: "Iced Tea",
      qty: 1
    }
  ]
};

const fakeOrders = [
  {
    id: 1,
    name: "Table 1",
    totalItems: 2
  },
  {
    id: 2,
    name: "Table 3",
    totalItems: 2
  }
];

const fakeApi = {
  getOrders() {
    return new Promise((resolve) =>
      setTimeout(() => resolve(fakeOrders), 3000)
    );
  },
  getOrderItems(id) {
    return new Promise((resolve) =>
      setTimeout(() => resolve(fakeOrderItems[id]), 3000)
    );
  }
};

class OrderItem extends PureComponent {
  render() {
    const { id, name, qty } = this.props;

    return (
      <div style={{ marginBottom: 10 }}>
        <span>
          {id}. {name} qty:{qty}
        </span>
      </div>
    );
  }
}

class OrderItemList extends PureComponent {
  state = {
    orderItems: []
  };

  componentDidMount() {
    fakeApi
      .getOrderItems(this.props.orderId)
      .then((orderItems) => this.setState({ orderItems }));
  }

  render() {
    const { orderItems } = this.state;

    if (!orderItems.length) {
      return <span>Loading orderItems...</span>;
    }

    return orderItems.map((item) => (
      <OrderItem key={item.id + item.name} {...item} />
    ));
  }
}

class Order extends PureComponent {
  render() {
    const { id, name } = this.props;
    return (
      <div style={{ marginBottom: 10 }}>
        <div>
          <span>Order #{id}</span>
        </div>
        <div>
          <span>For table {name}</span>
        </div>
        <OrderItemList orderId={id} />
      </div>
    );
  }
}

class OrderList extends PureComponent {
  state = {
    orders: []
  };

  componentDidMount() {
    fakeApi.getOrders().then((orders) => this.setState({ orders }));
  }

  render() {
    const { orders } = this.state;

    if (!orders.length) {
      return <div>Loading orders...</div>;
    }

    return orders.map((order) => <Order key={order.id} {...order} />);
  }
}

export default function App() {
  return <OrderList />;
}