Sharon
Sharon

Reputation: 3919

How do I update an array which is within an async function in a useEffect hook?

I'm working on a React app, which connects to a firebase database. Part of the app reads in a list of Items belonging to the current User, and then reads a config object in for each one, and finally updates the items in state. I'm having trouble getting it to work - I seem to keep getting

My component is:

const Dashboard = () => {

  const authUser = useContext(AuthUserContext);
  const firebase = useContext(FirebaseContext);

  const [error, setError]                   = useState<string|null>(null);
  const [firebaseToken, setFirebaseToken]   = useState<string|null>(null);
  const [items, setItems]                   = useState<Array<ItemModel>>([]);

  // On first render, get all Items
  useEffect(() => {

    if(!authUser) return;
    if(!firebase) return;
    
    let userId = authUser.id;

    const getItems = () => {

      firebase.doGetIdToken()
      .then((token) => {

        // Save token so it can be passed down
        setFirebaseToken(token);

        url = "items/" + userId;
        Client.getData(url, token)
        .then((itemResults:Array<ItemModel>) => {

            // Get config for each Item
            // Set up an empty array to hold the new data
            const itemResultsWithConfigs:Array<ItemModel> = []
            // Now get the config for each item
            itemResults.forEach((item:ItemModel) => {

                // Get config for this Item
                url = "/items/config/" + item.id;
                Client.getData(url, token)
                .then((newConfig:ConfigModel) => {

                  let newItem:ItemModel = {
                    ...item, 
                    config:  newConfig
                  }

                  // Add full item to list & update list
                  itemResultsWithConfigs.push(newItem);

                })

              })
              
              setItems(itemResultsWithConfigs);

            })

          });

        })
      })
      .catch(() => setError("Unable to connect to database"))
    }

    getItems();

  }, [authUser, firebase])

  return (
    <>
      <ul>
        {
          items.map((item:ItemModel) => {
              return <li key={item.id}>{item.name}</li>
          })
        }
      </ul>
    </>
  );
}

export default Dashboard;

Client.getData is:

async function getData(path:string, token:string) {

  const object:AxiosRequestConfig = {
    ...obj,
    method: 'GET',
    headers: {
      ...obj.headers,
      'Authorization': `Bearer ${token}`,
    },
  };

  try {
    const response:AxiosResponse      = await axios.get(`${baseUrl}${path}`, object);
    checkStatus(response);
    const parsedResult  = parseJSON(response);
    return parsedResult;

  } catch (error) {
    throw error;
  }
    
}

The problem is that the async function (getData) is returning at different times, therefore the array of items is being overwritten some of the time. Currently this is only rendering one or two of the items instead of the 3 that I know should be there.

How do I approach this?

Upvotes: 0

Views: 605

Answers (1)

Shubham Khatri
Shubham Khatri

Reputation: 281784

Since itemResultsWithConfig is derived asynchronously, a good idea is to map and wait for all the promises to resolve using Promise.all

const getItems = () => {

  firebase.doGetIdToken()
  .then((token) => {

    // Save token so it can be passed down
    setFirebaseToken(token);

    url = "items/" + userId;
    Client.getData(url, token)
    .then((itemResults:Array<ItemModel>) => {

        // Get config for each Item
        // Set up an empty array to hold the new data
        // Now get the config for each item
        let promises = itemResults.map((item:ItemModel) => {

            // Get config for this Item
            url = "/items/config/" + item.id;
            return Client.getData(url, token)
            .then((newConfig:ConfigModel) => {

              let newItem:ItemModel = {
                ...item, 
                config:  newConfig
              }

              return newItem;

            })

          })
          Promise.all(promises).then((itemResultsWithConfigs:Array<ItemModel>) => setItems(itemResultsWithConfigs))
          

        })

      });

    })

Upvotes: 2

Related Questions