In today’s fast‐paced digital economy, enterprise leaders must ensure that backend systems and user interfaces are seamlessly connected. Efficient API integration is no longer a technical afterthought it’s a strategic asset. Leveraging state‐of‐the‐art tools like Tanstack Query can significantly improve data consistency, reduce latency, and streamline product data management across large organizations.
Why Tanstack Query Matters for Enterprise Product Management
Tanstack Query excels in handling asynchronous server state, caching, and background data synchronization. For decision-makers, these features translate into:
- Operational Agility: Real-time updates and reduced API overhead empower businesses to react swiftly to market changes.
- Improved User Experience: Consistent data handling ensures that end users and internal teams alike work with the most up-to-date information.
- Lower Maintenance Costs: By centralizing data fetching and error handling, development teams can reduce redundancy and mitigate risks associated with stale data.
Setting Up Tanstack Query and Dev Tools
Before diving into queries and mutations, it’s essential to set up Tanstack Query and its accompanying developer tools. Run the following commands in your project directory:
npm install @tanstack/react-query
npm install @tanstack/react-query-devtools
Next, initialize your QueryClient at the root of your application:
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
const queryClient = new QueryClient();
function App() {
return (
<QueryClientProvider client={queryClient}>
<YourAppComponents />
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
);
}
export default App;
This setup is critical for efficient state management and debugging in production-level applications.
Code Walkthrough: Re-Ordered Product API Integration
Below is a series of re-ordered and annotated code examples that demonstrate how to integrate your product APIs with Tanstack Query. (Note: In these samples, every instance of “discount” has been replaced by “Product” to align with your current use-case.)
1. API Request Helper
This utility function standardizes API calls, providing consistent error handling and response parsing.
// api-request.ts
import { toast } from "sonner";
/**
* Helper function for API requests.
* Handles generic error messaging and returns JSON response.
*/
export const apiRequest = async (url: string, options: RequestInit) => {
const response = await fetch(url, options);
if (!response.ok) {
const errorData = await response.json();
toast.error(
"Error: " + (errorData?.res?.message?.message || "Something went wrong")
);
}
return response.json();
};
2. Product API Functions
Re-ordered for clarity, these functions perform CRUD operations on product data. They use helper methods to retrieve company-specific tokens and IDs (analogous to host values).
// product-api.ts
import { Product } from "@/types/product"; // Custom type for Product data
import { Api } from "@/utils/api/api";
import { apiRequest } from "@/utils/api/api-request";
import { getCompanyId } from "@/utils/get-company-id"; // Similar to getHostId
import { getCompanyToken } from "@/utils/token-utils"; // Similar to getHostToken
const BASE_URL = `${Api}/products`;
/**
* Fetch all active products for a company.
*/
export const getAllProductsApi = async () => {
return apiRequest(`${BASE_URL}/company/${getCompanyId()}?is_active=true`, {
method: "GET",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${getCompanyToken()}`,
},
});
};
/**
* Fetch a single product by ID.
*/
export const getSingleProductApi = async (id: string) => {
return apiRequest(`${BASE_URL}/${id}`, {
method: "GET",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${getCompanyToken()}`,
},
});
};
/**
* Create a new product.
*/
export const createProductApi = async (data: Product) => {
return apiRequest(BASE_URL, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${getCompanyToken()}`,
},
body: JSON.stringify(data),
});
};
/**
* Update an existing product by ID.
*/
export const updateProductApi = async (id: string, data: Product) => {
return apiRequest(`${BASE_URL}/${id}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${getCompanyToken()}`,
},
body: JSON.stringify(data),
});
};
/**
* Delete a product by ID.
*/
export const deleteProductApi = async (id: string) => {
return apiRequest(`${BASE_URL}/${id}`, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${getCompanyToken()}`,
},
});
};
3. Custom Hooks with Tanstack Query
These hooks abstract API integration logic using Tanstack Query’s useQuery
and useMutation
for optimal data fetching and mutation handling. Notice the consistent naming each hook is purpose-built for product operations.
// use-products.ts
import { Product } from "@/types/product";
import {
createProductApi,
getAllProductsApi,
getSingleProductApi,
updateProductApi,
deleteProductApi,
} from "@/utils/api/product-api";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { toast } from "sonner";
/**
* Hook to fetch all products.
*/
export const useGetAllProducts = () => {
return useQuery({
queryKey: ["products"],
queryFn: getAllProductsApi
});
};
/**
* Hook to fetch a single product by ID.
*/
export const useGetSingleProduct = (id: string) => {
return useQuery({
queryKey: ["product", id],
queryFn: () => getSingleProductApi(id),
enabled: !!id, // Prevents execution if ID is missing
});
};
/**
* Hook to create a new product.
*/
export const useCreateProduct = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (productData: Product) => createProductApi(productData),
onSuccess: () => {
toast.success("✅ Product created successfully!");
queryClient.invalidateQueries({ queryKey: ["products"] }); // Refreshes product list
},
onError: () => {
toast.error("❌ Failed to create product");
},
});
};
/**
* Hook to update an existing product.
*/
export const useUpdateProduct = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({
_id,
productData,
}: {
_id: string;
productData: Product;
}) => updateProductApi(_id, productData),
onSuccess: () => {
toast.success("✅ Product updated successfully!");
queryClient.invalidateQueries({ queryKey: ["products"] });
queryClient.invalidateQueries({ queryKey: ["product"] });
},
onError: () => {
toast.error("❌ Failed to update product");
},
});
};
/**
* Hook to delete a product.
*/
export const useDeleteProduct = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (id: string) => deleteProductApi(id),
onSuccess: () => {
toast.success("✅ Product deleted successfully!");
queryClient.invalidateQueries({ queryKey: ["products"] });
},
onError: (error: any) => {
toast.error(`❌ Failed to delete product: ${error.message}`);
},
});
};
4. Practical Use Cases: React Components
Here are a few components that demonstrate how the hooks can be integrated into your UI. Each component is designed to be concise and focus on a single product operation.
Product List Component
// ProductList.tsx
import React from "react";
import { useGetAllProducts } from "@/hooks/use-products";
const ProductList = () => {
const { data, isLoading, error } = useGetAllProducts();
if (isLoading) return <p>Loading...</p>;
if (error) return <p>Error: {error.message}</p>;
return (
<ul>
{data?.res.products.map((product: any) => (
<li key={product._id}>
{product.product_code} - {product.product_name}
</li>
))}
</ul>
);
};
export default ProductList;
Product Details Component
// ProductDetails.tsx
import React from "react";
import { useGetSingleProduct } from "@/hooks/use-products";
const ProductDetails = ({ id }: { id: string }) => {
const { data, isLoading, error } = useGetSingleProduct(id);
if (isLoading) return <p>Loading...</p>;
if (error) return <p>Error: {error.message}</p>;
return (
<div>
<h3>Product Code: {data?.res.product.product_code}</h3>
<p>Name: {data?.res.product.product_name}</p>
<p>Description: {data?.res.product.description}</p>
</div>
);
};
export default ProductDetails;
Create Product Component
// CreateProduct.tsx
import React, { useState } from "react";
import { useCreateProduct } from "@/hooks/use-products";
import { Product } from "@/types/product";
const CreateProduct = () => {
const mutation = useCreateProduct();
const [productData, setProductData] = useState<Product>({
_id: "",
company_user_id: "123", // Example company user ID
product_code: "",
product_name: "",
description: "",
price: 0,
stock: 0,
// Additional product details as needed
});
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
mutation.mutate(productData);
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
placeholder="Product Code"
value={productData.product_code}
onChange={(e) =>
setProductData({ ...productData, product_code: e.target.value })
}
/>
<input
type="text"
placeholder="Product Name"
value={productData.product_name}
onChange={(e) =>
setProductData({ ...productData, product_name: e.target.value })
}
/>
<button type="submit" disabled={mutation.isLoading}>
{mutation.isLoading ? "Creating..." : "Create Product"}
</button>
{mutation.error && <p>Error: {mutation.error.message}</p>}
</form>
);
};
export default CreateProduct;
Update Product Component
// UpdateProduct.tsx
import React, { useState } from "react";
import { useUpdateProduct } from "@/hooks/use-products";
import { Product } from "@/types/product";
const UpdateProduct = ({ id }: { id: string }) => {
const mutation = useUpdateProduct();
const [updatedData, setUpdatedData] = useState<Partial<Product>>({
price: 100, // Default updated value
});
const handleUpdate = () => {
mutation.mutate({ _id: id, productData: updatedData as Product });
};
return (
<div>
<button onClick={handleUpdate} disabled={mutation.isLoading}>
{mutation.isLoading ? "Updating..." : "Update Product"}
</button>
{mutation.error && <p>Error: {mutation.error.message}</p>}
</div>
);
};
export default UpdateProduct;
Delete Product Button (Integrated in List)
// ProductDeleteButton.tsx
import React from "react";
import { useDeleteProduct } from "@/hooks/use-products";
const ProductDeleteButton = ({ id }: { id: string }) => {
const deleteMutation = useDeleteProduct();
const handleDelete = (id: string) => {
if (window.confirm("Are you sure you want to delete this product?")) {
deleteMutation.mutate(id);
}
};
return (
<button onClick={() => handleDelete(id)} className="bg-red-500 text-white px-3 py-1 rounded">
Delete
</button>
);
};
export default ProductDeleteButton;
Strategic Considerations for C-suite Leaders
Adopting an API integration strategy that leverages Tanstack Query can yield significant business advantages:
- Scalability & Resilience: By decoupling UI from backend services through asynchronous queries, your company can scale operations and introduce new features with minimal friction.
- Data Integrity: Real-time synchronization ensures that product information remains accurate across all channels, aiding both internal decision-making and customer engagement.
- Operational Efficiency: Streamlined API error handling and automated query refetching reduce downtime and free up developer resources for innovation.
Conclusion
Integrating product APIs with Tanstack Query isn’t just a technical upgrade it’s a strategic enabler for enterprise growth. With a clear setup process, robust error handling, and modular hooks for CRUD operations, your organization can deliver a more reliable, scalable, and agile digital ecosystem.
By aligning technology with business strategy, your leadership can drive competitive advantage and position your company for long-term success in a data-driven market.
Top comments (0)