Website logo

07 Aug 2023

Using
React
Query
to
Processing
Data
to
the
Server

Web page not only able to show the data from the server, but also able to create new data, update the data, or even delete the existing data. For example, adding a product to the cart in e-commerce website.

const ProductDetailPage = () => {
  const [isLoading, setIsLoading] = useState(false)

  const buy = async (productId) => {
    try {
      setIsLoading(true)

      const response = await axios.post('/orders', {
        product_id: productId,
      })

      alert('Product successfully added to cart.')
    } catch (error) {
      alert('Failed to add the product to cart.')
    } finally {
      setIsLoading(false)
    }
  }

  return (
    <div>
      {/* Show product detail here. */}

      {!isLoading && (
        <button type="button" onClick={() => buy(10)}>
          Add to Cart
        </button>
      )}
      {isLoading && (
        // Show loading here.
      )}
    </div>
  )
}

We can see that those implementation have many lines of code especially when we need to add the same logic for other pages in a website. Also sometimes we need to implement a technique called optimistic update. That's become complicated.

Solution

Not only for fetching the data, React Query also can be used to create new data, update the data, or even delete the existing data. First we can create a new custom hook in a new file, use-buy-product-mutation.js for example.

const action = async (payload) => {
  const response = await axios.post('/orders', {
    product_id: payload.productId,
  })

  return response
}

const useBuyProductMutation = () => useMutation(action)

Then later we can use our custom hook that we already created in the any pages or components that need it.

const ProductDetailPage = () => {
  const buyProductMutation = useBuyProductMutation()

  return (
    <div>
      {/* Show product detail here */}

      {!buyProductMutation.isLoading && (
        <button
          type="button"
          onClick={() => {
            buyProductMutation.mutate(
              {
                productId: 10
              },
              {
                onSuccess: () => {
                  alert('Product successfully added to cart.')
                },
                onError: () => {
                  alert('Failed to add the product to cart.')
                },
              }
            )
          }}>
          Tambahkan
        </button>
      )}
      {buyProductMutation.isLoading && (
        // Show loading here.
      )}
    </div>
  )
}

We can also implement technique called optimistic update like below if we need it. Optimistic update not only for creating a new data only, but also for updating the data, or even deleting the existing data. Optimistic update implementation is a bit complicated, but a lot more complicated if you are not using React Query.

const action = async (payload) => {
  const response = await axios.post('/orders', {
    product_id: payload.productId,
  })

  return response
}

const useBuyProductMutation = () => {
  const queryClient = useQueryClient()

  return useMutation(action, {
    onMutate: async (payload) => {
      // Cancel a query that used to get order list.
      await queryClient.cancelQueries(['orders'])

      // Get order list data that already fetched
      // and saved in the cache before.
      // Let's say we already implement a query
      // to get order list: useOrdersQuery.
      const previousOrders = queryClient.getQueryData(['orders'])
      // Get ourself's account data that logged in now
      // that already fetched and saved in the cache before.
      // Let's say we already implement a query to get
      // ourself's account data that logged in: useMeQuery.
      const previousMe = queryClient.getQueryData(["me"])

      // Get a value that passed in as an argument
      // in the .mutate() method that used in a page or component.
      const {
        productId,
        slug,
        title,
        price,
        thumbnail
      } = payload

      // Need to add a dummy data to cache owned by
      // a query with query key ['orders'] that is useOrdersQuery
      // while the request process to /orders run.
      if (previousOrders && previousMe) {
        const newOrder = {
          id: Date.now(),
          user_id: previousMe.id,
          product_id: productId,
          title,
          price,
          thumbnail,
        }

        const data = [
          ...previousOrders,
          newOrder,
        ]

        queryClient.setQueryData(['orders'], data)
      }

      return { previousOrders }
    },
    onError: (error, payload, context?: Context) => {
      // If errors happens when request to the /orders,
      // order list data that lives in the cache will be reverted back
      // to remove the dummy data.
      if (context?.previousOrders) {
        queryClient.setQueryData(['orders'], context.previousOrders)
      }
    },
    onSettled: () => {
      // After success or error, we need to synchronize
      // to get the latest order list data.
      // Query with query key ['orders'] that is useOrdersQuery
      // will fetching data in the background.
      queryClient.invalidateQueries(['orders'])
    },
  })
}

Then we can use it like this.

const ProductDetailPage = () => {
  const buyProductMutation = useBuyProductMutation();

  return (
    <div>
      {/* Show product detail here. */}

      <button
        type="button"
        disabled={buyProductMutation.isLoading}
        onClick={() => {
          buyProductMutation.mutate(
            {
              productId: 10,
              title: "Product A",
              price: 500,
              thumbnail: "IMAGE_URL_HERE",
            },
            {
              onSuccess: () => {
                alert("Product successfully added to cart.");
              },
              onError: () => {
                alert("Failed to add the product to cart.");
              },
            }
          );
        }}
      >
        Add to Cart
      </button>
    </div>
  );
};

To have a better understanding about optimistic update, you can read this article.

Conclusion

Here we can see that React Query not only able to fetching the data, but also helps us to create new data, update the data, or even delete the existing data. Information on the above only explain small parts of things that React Query able to do, because there are so many features that React Query provide for us. One more time I remind you, it will be hard at start, but believe me, React Query is really useful and worth to invest your time for learning it.