Search code examples
javascriptreactjsasync-awaites6-promise

How to use async await in React components without useEffect


My app has a search bar where the user types in a word and clicks search. Upon click, the app fetches the definition of that word from an open dictionary API, and then updates the parent component's state to reflect the results. The component is then supposed to render the results by passing that as a prop to a presentational child component.

However, it looks like the state gets set before the fetch call has the time to return the data. So the search results are not rendered until the search button is clicked again, causing confusion.

I have tried resolving this by making my function asynchronous (Dictionary.search refers to an imported function which handles the fetch and returns the result in the form of an array):

async search(term) {
    const results = await Dictionary.search(term);
    this.setState({ searchTerm: term, results: results });
  }

However this only works some of the time. For longer searches, it doesn't wait before updating the state and re-rendering.

I have done some googling and came across this suggestion, but it also doesn't work:

search = (term) => {
    const dictionarySearch = async (term) => {
      const results = await Dictionary.search(term);
      this.setState({ searchTerm: term, results: results });
    };
    dictionarySearch(term);
};

EDITED TO ADD: Here is the Dictionary.search code, along with its helper function:

//Create an empty array in which the results of the API call will be stored
let results = [];

const Dictionary = {
  search(term) {
    //Url with our search term and API key:
    const url = `https://www.dictionaryapi.com/api/v3/references/collegiate/json/${term}?key=${api_key}`;

    //Fetch the results from Merriam Webster API:
    fetch(url)
      .then((response) => {
        //Catch an error from their server
        if (!response.ok) {
          throw new Error("Network response was not ok");
        }
        //Return javaScript object version of the response
        return response.json();
      })
      .then((jsonResponse) => {
        //Perform the helper function on the javascript object and return the result (array)
        return shortDef(jsonResponse);
      })
      //Catch any other errors than a server error
      .catch((error) => {
        console.error(
          "There has been a problem with your fetch operation:",
          error
        );
      });

    //Create a copy of the results array
    let returnResults = results.slice();
    //Reset the original array to an empty array
    results = [];
    //Return the copy
    return returnResults;
  },
};

//Helper function to extract only certain info from the API
function shortDef(response) {
  response.forEach((object) => {
    //Create a new object for each object int he response array
    let result = { word: "", type: "", definitions: [] };
    //Add the word and type to the object
    result.word = object.hwi.hw;
    result.type = object.fl;
    //Add the definitions to the object. There may be several, so it is pushed to an array.
    let defs = object.shortdef;
    defs.forEach((def) => {
      result.definitions.push(def);
    });

    //Add the object to the array of API results
    results.push(result);
  });
  //Return the list of results
  return results;
}

I don't want to call the API in the ComponentDidMount, because it should get called every time the user presses "search". I also would prefer not to use useEffect, as it would mean refactoring my entire component from a class to a function.

Is there no way to have the setState in a class component wait for an asynchronous task to complete?


Solution

  • The problem is that your Dictionary.search function immediately returns, because it does not wait until the .then block resolves. Change it to an async function and await the fetch of the url. It should look like this:

    const Dictionary = {
      // Make search an async function
      search: async term => {
        const url = `https://www.dictionaryapi.com/api/v3/references/collegiate/json/${term}?key=${api_key}`;
    
        // Await the results
        await fetch(url)
          .then(response => {
            // ...
          })
          .then(jsonResponse => {
            // ...
          })
          .catch(error => {
            // ...
          });
    
        return;
      },
    };