3

I have a function that fetches from a url in React

const DataContextProvider = (props) => {
  const [isLoading, setLoading] = useState(false);
  const [cocktails, setCocktails] = useState([]);

  useEffect(() => {
    const fetchCocktailList = async () => {
      const baseUrl = 'https://www.thecocktaildb.com/api/json/v1/1/';
      setLoading(true);
      try {
        const res = await fetch(`${baseUrl}search.php?s=margarita`);
        const data = await res.json();
        console.log(data);
        setCocktails(data.drinks);
        setLoading(false);
      } catch (err) {
        console.log('Error fetching data');

        setLoading(false);
      }
    };

    fetchCocktailList();
  }, []);


How I'm mapping data so far.

const DrinkList = () => {
  const { cocktails } = useContext(DataContext);
  return (
    <div className='drink-list-wrapper'>
      {cocktails.length > 0 &&
        cocktails.map((drink) => {
          return <DrinkItem drink={drink} key={drink.idDrink} />;
        })}
    </div>
  );
};

However I want to fetch from this url also ${baseUrl}search.php?s=martini

I would like a good clean way to do this and set my state to both of the returned data.

7
  • When you fetch martini and setCocktails with that response should it replace the margarita response, or do you want it to add these drinks to some object/array? Commented Feb 14, 2020 at 5:12
  • It should not replace. I want cocktails to contain both martinis and margaritas. So I can map them. Commented Feb 14, 2020 at 5:14
  • How do you want to handle errors in individual network requests? Should the entire operation fail if one request fails or should it succeed with partial data? Commented Feb 14, 2020 at 5:15
  • Ok, then can you please also share your component code so we see what your state objects look like so setCocktails can properly merge in the new drinks? Commented Feb 14, 2020 at 5:16
  • My first would be it should fail. However I am learning so if its best practice to handle separately then I would go with that. Commented Feb 14, 2020 at 5:17

3 Answers 3

4

First base the data fetch function on a parameter:

const fetchCocktail = async (name) => {
  const baseUrl = 'https://www.thecocktaildb.com/api/json/v1/1/';
  try {
    const res = await fetch(`${baseUrl}search.php?s=` + name);
    const data = await res.json();
    return data.drinks;
  } catch (err) {
    console.log('Error fetching data');
  }
}

Then use Promise.all to await all results:

setLoading(true);
var promises = [
  fetchCocktail(`margarita`),
  fetchCocktail(`martini`)
];
var results = await Promise.all(promises);
setLoading(false);
DrinkList(results);

Where results will be an array with the responses that you can use on the DrinkList function.

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

5 Comments

This is what I was going to respond with, but would like to note that Promise.allSettled will allow you to handle mixed success/failure, whereas Promise.all will reject for any failure
You should probably also setLoading(false) after the try/catch in the finally for cleanup
Nevermind, you keep changing your code. I'll stop commenting until you've finalized all of your edits.
@jsejcksn finished :D
I've tried this and its crashing the browser. Maybe because of an infinite loop due to the useEffect hook?
0

Here's a method which will let you specify the cocktail names as dependencies to the useEffect so you can store them in your state and fetch new drink lists if you want new recipes. If not, it'll just be a static state variable.

  • I've also added another state variable errorMessage which you use to pass an error message in the case of failure.
  • Also, you should include the appropriate dependencies in your useEffect hook. The setState functions returned by calls to useState are stable and won't trigger a re-run of the effect, and the cocktailNames variable won't trigger a re-run unless you update it with new things to fetch.
const DataContextProvider = (props) => {
  const [isLoading, setLoading] = useState(false);
  const [cocktails, setCocktails] = useState([]);
  const [errorMessage, setErrorMessage] = useState(''); // holds an error message in case the network request dosn't succeed
  const [cocktailNames, setCocktailNames] = useState(['margarita', 'martini']); // the search queries for the `s` parameter at your API endpoint

  useEffect(() => {
    const fetchCocktailLists = async (...cocktailNames) => {
      const fetchCocktailList = async (cocktailName) => {
        const baseUrl = 'https://www.thecocktaildb.com/api/json/v1/1/search.php';
        const url = new URL(baseUrl);
        const params = new URLSearchParams({s: cocktailName});
        url.search = params.toString(); // -> '?s=cocktailName'
        const res = await fetch(url.href); // -> 'https://www.thecocktaildb.com/api/json/v1/1/search.php?s=cocktailName'
        const data = await res.json();
        const {drinks: drinkList} = data; // destructured form of: const drinkList = data.drinks;
        return drinkList;
      };

      setLoading(true);
      try {
        const promises = [];
        for (const cocktailName of cocktailNames) {
          promises.push(fetchCocktailList(cocktailName));
        }
        const drinkLists = await Promise.all(promises); // -> [[drink1, drink2], [drink3, drink4]]
        const allDrinks = drinkLists.flat(1); // -> [drink1, drink2, drink3, drink4]
        setCocktails(allDrinks);
      }
      catch (err) {
        setErrorMessage(err.message /* or whatever custom message you want */);
      }
      setLoading(false);
    };

    fetchCocktailList(...cocktailNames);
  }, [cocktailNames, setCocktails, setErrorMessage, setLoading]);
};

3 Comments

This works after a quick copy and paste to check. But embarrassing for me as I don't understand parts of it. Such as URLSearchParams, using .flat ect and could never explain it to anyone! Is there a way of stripping this down to a more beginner friendly level?
It's written that way to be extensible and as a model for learning. There are definitely parts that can be simplified for a more hard-coded approach, but is that what you really want? URLSearchParams handles encoding of the search parameters for you. If you want to add more parameters, just add to the object argument in the constructor call. array.flat() just flattens a multi-dimensional array however many levels you ask it to. Check out the MDN articles on those two to understand them more. I'll add a few comments to the code.
Good luck! Some React concepts can be kind of mind-bending. Docs, docs, docs...
0

var promises = [ fetchCocktail(api1), fetchCocktail(api2) ]; var results = await Promise.allSettled(promises);

Comments

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.