0

I am making dummy app to test server side API. First request returns nested JSON object with Product names and number of variants that it has. From there I extract Product name so I can send second request to fetch list of variants with product images, sizes etc.

first request

second request

Sometimes it will load and display variants from only one product but most of the times it will work correctly and load all variants from both dummy products. Is there a better way of doing this to ensure it works consistently good. Also I would like to know if there is a better overall approach to write something like this.

Here is the code:

  import React, { useEffect, useState } from "react";
import axios from "axios";

import ShirtList from "../components/ShirtList";

const recipeId = "15f09b5f-7a5c-458e-9c41-f09d6485940e";

const HomePage = props => {
  const [loaded, setLoaded] = useState(false);

  useEffect(() => {
    axios
      .get(
        `https://api.print.io/api/v/5/source/api/prpproducts/?recipeid=${recipeId}&page=1`
      )
      .then(response => {
        let shirtList = [];
        const itemsLength = response.data.Products.length;
        response.data.Products.forEach((element, index) => {
          axios
            .get(
              `https://api.print.io/api/v/5/source/api/prpvariants/?recipeid=${recipeId}&page=1&productName=${element.ProductName}`
            )
            .then(response => {
              shirtList.push(response.data.Variants);
              if (index === itemsLength - 1) {
                setLoaded(shirtList);
              }
            });
        });
      });
  }, []);

  const ListItems = props => {
    if (props.loaded) {
      return loaded.map(item => <ShirtList items={item} />);
    } else {
      return null;
    }
  };

  return (
    <div>
      <ListItems loaded={loaded} />
    </div>
  );
};

export default HomePage;
2
  • 1
    Use async/await for each request in list Commented Mar 10, 2020 at 10:09
  • 1
    isn't .then() same? Commented Mar 10, 2020 at 10:30

2 Answers 2

2

You are setting the loaded shirts after each iteration so you will only get the last resolved promise data, instead fetch all the data and then update the state.

Also, separate your state, one for the loading state and one for the data.

Option 1 using async/await

const recipeId = '15f09b5f-7a5c-458e-9c41-f09d6485940e'
const BASE_URL = 'https://api.print.io/api/v/5/source/api'

const fetchProducts = async () => {
  const { data } = await axios.get(`${BASE_URL}/prpproducts/?recipeid=${recipeId}&page=1`)
  return data.Products
}

const fetchShirts = async productName => {
  const { data } = await axios.get(
    `${BASE_URL}/prpvariants/?recipeid=${recipeId}&page=1&productName=${productName}`,
  )
  return data.Variants
}

const HomePage = props => {
  const [isLoading, setIsLoading] = useState(false)
  const [shirtList, setShirtList] = useState([])

  useEffect(() => {
    setIsLoading(true)

    const fetchProductShirts = async () => {
      const products = await fetchProducts()
      const shirts = await Promise.all(
        products.map(({ productName }) => fetchShirts(productName)),
      )
      setShirtList(shirts)
      setIsLoading(false)
    }

    fetchProductShirts().catch(console.log)
  }, [])
}

Option 2 using raw promises

const recipeId = '15f09b5f-7a5c-458e-9c41-f09d6485940e'
const BASE_URL = 'https://api.print.io/api/v/5/source/api'

const fetchProducts = () =>
  axios.get(`${BASE_URL}/prpproducts/?recipeid=${recipeId}&page=1`)
    .then(({ data }) => data.Products)


const fetchShirts = productName =>
  axios
    .get(
      `${BASE_URL}/prpvariants/?recipeid=${recipeId}&page=1&productName=${productName}`,
    )
    .then(({ data }) => data.Variants)

const HomePage = props => {
  const [isLoading, setIsLoading] = useState(false)
  const [shirtList, setShirtList] = useState([])

  useEffect(() => {
    setIsLoading(true)

    fetchProducts
      .then(products) =>
        Promise.all(products.map(({ productName }) => fetchShirts(productName))),
      )
      .then(setShirtList)
      .catch(console.log)
      .finally(() => setIsLoading(false)
  }, [])
}

Now you have isLoading state for the loading state and shirtList for the data, you can render based on that like this

return (
  <div>
    {isLoading ? (
      <span>loading...</span>
      ) : (
      // always set a unique key when rendering a list.
      // also rethink the prop names
      shirtList.map(shirt => <ShirtList key={shirt.id} items={shirt} />)
    )}
  </div>
)

Refferences

Promise.all

Promise.prototype.finally

React key prop

Sign up to request clarification or add additional context in comments.

4 Comments

Thank you Asaf for your kind and descriptive answer. I am learning with you my friend :)
I see you used destructuring. What's the benefit of using it here???
@daniel.tosaba Nothing more than making the code shorter
Cool. And it the fetchProducts function you made typo right?? you are supposed to pass { data } and return data.Products :)
1

The following should pass a flat array of all variants (for all products ) into setLoaded. I think this is what you want.

Once all the products have been retrieved, we map them to an array of promises for fetching the variants.

We use Promise.allSettled to wait for all the variants to be retrieved, and then we flatten the result into a single array.

useEffect(()=>(async()=>{
        const ps = await getProducts(recipeId)
        const variants = takeSuccessful(
            await Promise.allSettled(
                ps.map(({ProductName})=>getVariants({ recipeId, ProductName }))))
        setLoaded(variants.flat())
    })())

...and you will need utility functions something like these:

const takeSuccessful = (settledResponses)=>settledResponses.map(({status, value})=>status === 'fulfilled' && value)
const productURL = (recipeId)=>`https://api.print.io/api/v/5/source/api/prpproducts/?recipeid=${recipeId}&page=1`
const variantsURL = ({recipeId, productName})=>`https://api.print.io/api/v/5/source/api/prpvariants/?recipeid=${recipeId}&page=1&productName=${productName}`

const getProducts = async(recipeId)=>
    (await axios.get(productURL(recipeId)))?.data?.Products

const getVariants = async({recipeId, productName})=>
    (await axios.get(variantsURL({recipeId,productName})))?.data?.Variants

1 Comment

Thank you very much.. I appreciate your answer.. I came across new concepts and it's joy to prosper with you my friend :)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.