Search code examples
reactjsreact-hooksopenai-api

React memoizing the fetch result and fetch again only if new item added to the list


I have a function that makes an API call with the product titles to OpenAI to generate a recipe when the button is clicked. I am getting the list of products from the basket and if no new product is added to the basket I just want to show the same result when the button is clicked instead of fetching again. I want to only fetch again when a new item is added to the product list(cart) and when the button is clicked.

I have tried using useMemo but even though I added the product list to the dependency array it fetches every time.

Here is my code so far;

const handleClicked = async (event) => {
    event.preventDefault();
   try {
      setLoading(true)
      setDisplayedMessage("Recipe is generating..");
      let productsList = "";

      cartItems.products.forEach((prod, index) => {
         productsList = productsList + prod.title + " ";
      });
      
      const res = await fetch("API_CALL",
      {
        method: "POST",
        headers: {
          "Content-Type": "application/json"
        },
        body: JSON.stringify({ prompt: "Generate recipe with this items " + productsList })
      });

      const { response: data } = await res.json();
      setResult(data.trimStart());

      if (data === "") {
         throw data.error || new Error(`Request failed with status ${data}`);
      }

      setDisplayedMessage("");
      openModal();

   }catch (e) {
     console.log(e)
   }
}

Solution

  • You guess it right, useMemo is designed for render optimization, but not quite as you thought

    There is even an equivalent hook for functions called useCallback, but in your case, it won't fix your issue, because useMemo and useCallback keep reference between renders so children components that gets those references won't update over renders (it will if the reference will update, which happens when one of the expressions in the dependency array -the second arg of those two hooks- updates)

    Your issue is not about references but about the execution of your event handler handleClicked. Even tho the function reference is the same through renders, at every click it will be executed.

    What you need, is a reference that you can compare with productsList to check if you want to fetch, and if so update this reference. To do so, you can use useRef

    Using useRef I would have done something like that

    const productsListRef = useRef('');
    
    const handleClicked = async (event) => {
      event.preventDefault();
    
      // Just a little bonus on how build your productList variable
      const productList = cartItems.products
      // Here we sort the array in order to make sure the order is always the same
      // So we don't trigger a fetch when the order is different
      .sort((prodA, prodB) => {
        return prodA.title > prodB.title ? 1 : -1;
      })
      // Thanks to array.prototype.reduce we can build a string
      // without reassigning the productList variable, so the code is less error prone
      .reduce((acc, prod) => {
        return `${acc}${prod.title} `;
      }, '');
    
      const shouldFetch = productsListRef.current !== productList;
    
      if (shouldFetch) {
        // fetch your data and do all your computation
        // [...]
        // then update the ref
        productsListRef.current = productList;
      }
    }