skip to Main Content

What do I need to do to make doSomethingWithItems() wait until after fetchItems() has completed?

const [items, setItems] = useState();

const fetchItems = async () => {
  try {
    let responseData = await sendRequest(`${process.env.REACT_APP_BACKEND_URL}/items/user/${userId}`);
    setItems(responseData.items);
  } catch (err) {}
};

const doSomethingWithItems = () => {
  const filteredItems = items.filter(x => x.title === 'something');
  setItems(filteredItems);
}

useEffect(() => {
  fetchItems(); // ** WAIT FOR THIS TO FINISH BEFORE MOVING ON **
  doSomethingWithItems();
}, []);

3

Answers


  1. Since fetchItems is async, it returns a Promise. Which you can follow with .then():

    useEffect(() => {
      fetchItems().then(doSomethingWithItems);
    }, []);
    

    Or, more expanded syntax to better understand the structure:

    useEffect(() => {
      fetchItems().then(() => {
        doSomethingWithItems();
      });
    }, []);
    

    However, your next problem is tracking state. Note that doSomethingWithItems depends on items. But state is not updated immediately. Instead of updating state in fetchItems, return the new value and pass it to doSomethingWithItems:

    const [items, setItems] = useState();
    
    const fetchItems = async () => {
      try {
        let responseData = await sendRequest(`${process.env.REACT_APP_BACKEND_URL}/items/user/${userId}`);
        return responseData.items;
      } catch (err) {
        // You probably want to do something meaningful here
        // At least return something:
        return [];
      }
    };
    
    const doSomethingWithItems = (newItems) => {
      const filteredItems = newItems.filter(x => x.title === 'something');
      setItems(filteredItems);
    }
    
    useEffect(() => {
      fetchItems().then(newItems => {
        doSomethingWithItems(newItems);
      });
    }, []);
    
    Login or Signup to reply.
  2. Try this

    useEffect(() => {
       (async()=>{
          await fetchItems(); 
          doSomethingWithItems();
       })();
    }, []);
    
    Login or Signup to reply.
  3. React documents that if you are calling asynchronous code from within a useEffect() that you are best to define an async function INSIDE the useEffect() and then call that function. This is what yah yaha’s solution is doing, though because it uses the Immediately Invoked Function Expression (IIFE) approach, it might not be obvious as:

    useEffect(() => {
      const doMyAsyncStuff = () => {
        await fetchItems();
        doSomethingWithItems();
      }
    
      doMyAsyncStuff();
    }, [....]);
    

    However, as David’s answer hints at: the next problem is that the "set state function" of useState() is asynchronous but it does not return a Promise. So this means that your code cannot await it or .then() it. You have NO WAY of knowing when the call to setItems() will actually finish…you cannot wait for it.

    Good news! React has a solution for that — React will RE-RENDER a component after its state gets updated by the "set state function".

    And as David suggests, you can have React do something in (react)ion to a specific state variable being modified with use of a useEffect() — "when the items state variable has a new value, here’s some code I want to run".

    React is a pretty simple system overall. But it takes a bit of wrapping your head around state variables, rendering, and JavaScript’s asynchronous code execution.

    I strongly encourage all of my friends (and make it a requirement of all of my coworkers) to watch the video JavaScript: Understanding the Weird Parts multiple times. Especially the parts about Synchronous and Asynchronous code execution (time links are in the video description).

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