Home > Software engineering >  How to get value from Promise & use that later?
How to get value from Promise & use that later?

Time:12-08

Let me explain the issue, I am trying to show some details in a modal. I am using table in the modal to show details. In one section, I need the product name which is returned from a database query and I have to use that value returned from the database. The code for table is as follows:

<tbody>
                            {item.cartItems.map((item, i) =>
                                <tr key={item._id}>
                                    <th scope="row">{i   1}</th>
                                    <th><img src={`${API}/product/photo/${item.product}`} alt={item.product.name} width="60px" height="50px" /></th>
                                    <td>{**data returned from database**}</td>
                                    <td align="center">{item.count}</td>
                                    <td align="center">৳ {item.price * item.count} </td>
                                </tr>
                            )}
                        </tbody>

To get the data from the database, I am using a function

const getProdcutName = id => {
    var productName;
    getProductDetails(id)
        .then(response => {
            productName = response.data.name;
        });
    return productName;
}

But I can't access the value. The main thing is, in every Iteration I need to send the {item.product} to getProductName(id) and I need the name from the database in return. But I can't access the data from promise scope to return it.

CodePudding user response:

There are a couple ways to handle this. One approach is to make use of a useEffect within this component.

Something like this should work within your current component:

const processCartItem = async (cartItem) => {
  const { product } = cartItem;
  const { data: { name: productName } } = await getProductName(product)
  return {...cartItem, productName}
}

const YourComponent = ({ item }) => {
   const [isLoading, setIsLoading] = useState(true)
   const [processedItems, setProcessedItems] = useState({})
   const { cartItems } = item;

   useEffect(() => {
      Promise.all(cartItems.map(processCartItem))
      .then(results => {
       setProcessedItems(results);
       setIsLoading(false);
      }); 
   }
   , [cartItems])
   
   if(loading) {
      return <span>Loading...</span>
   }

   return (
   <tbody>
    {processedItems.map((item, i) =>
      <tr key={item._id}>
          <th scope="row">{i   1}</th>
          <th><img src={`${API}/product/photo/${item.product}`} alt={item.product.name} width="60px" height="50px" /></th>
          <td>{item.productName}</td>
          <td align="center">{item.count}</td>
          <td align="center">৳ {item.price * item.count} </td>
      </tr>
    )}
    </tbody>
  );
}
  

Essentially you use useEffect to process the cartItems (assuming these come from props or state). Promise.all will wait for all of the getProductName requests to complete at which point it will return the results in an array (I've modified the function so it also returns the original data, adding in the product name).

I've also added a loading state - since you don't have this data when you land on your component, you should show some feedback to the user.

An alternative to this would be to create a separate component for each td product name element. In that component you prop in the product and handle loading state within. This has the effect of not blocking rendering of the entire table:

const ProductName = ({ product }) => {
  const [isLoading, setIsLoading] = useState(true);
  const [productName, setProductName] = useState('');

  useEffect(() => {
    getProductName(product).then(({ data: { name } }) => 
      setProductName(name);
      setIsLoading(false)
    )
  }, [product])
 
  if(isLoading){ return <td>Loading...</td> }

  return <td>{productName}</td>
}

I think the first method generally makes more sense, which is to show a loading state for the entire table before rendering. The number of items you need to load could increase and you end up with loading indicators everywhere which is a bad look. Unless for example you have all the data but need to make some api request for images, in that case maybe you take the second approach.

CodePudding user response:

This is the solution to my issue. Thanks to everyone who helped me out.

const CartItem = ({ item, i }) => {
const [productName, setProductName] = useState();

useEffect(() => {
    getProductDetails(item.product)
        .then(res => { setProductName(res.data.name) });
}, []);

return (
    <tr key={item._id}>
        <th scope="row">{i   1}</th>
        <th><img src={`${API}/product/photo/${item.product}`} alt={item.product.name} width="60px" height="50px" /></th>
        <td>{productName == undefined ? "Getting Name" : productName}</td>
        <td align="center">{item.count}</td>
        <td align="center">৳ {item.price * item.count} </td>
    </tr>
)}

And to send items to the cartItem

<tbody>
                            {item.cartItems.map((item, i) =>
                                <CartItem item={item} i={i} key={item._id} />
                            )}
                        </tbody>
  • Related