Escaping React's useEffect Hell
Table of contents
Problem
In the world of React development, managing dependent queries often leads to a tangle of useEffect
calls, making the codebase less clear and harder to handle. This becomes especially challenging when dealing with multiple API calls that rely on the results of previous queries. Let's explore a more straightforward solution to improve code readability and maintainability.
Show me the Code
Consider a practical example on a hypothetical "Cart" page, where the interaction with three APIs is necessary:
GET
-/carts
[Get all the types of Carts]GET
-/cart-items
[Get cart items for a particular Cart ID]GET
-/product-offers
[Get product offers for those particular cart items]
import { useEffect } from 'react';
import { useGetCartsQuery, useLazyGetCartItemsQuery, useLazyGetProductOffersQuery } from './api'; // Replace with your RTK Query API file
const ShoppingCart = () => {
const { data: carts, isSuccess: cartsSuccess } = useGetCartsQuery();
const [ getCartItems, getCartItemStates ] = useLazyGetCartItemsQuery();
const [ getProductOffers, getProductOfferStates ] = useLazyGetProductOffersQuery();
useEffect(() => {
if (cartsSuccess) {
const cartId = cartItems?.id;
getCartItems({id: cartId})
}
}, [cartsSuccess]);
useEffect(() => {
if (cartItemsSuccess) {
getProductOfferStates({ productIds: cartItems?.map(item => item.productId) })
}
}, [cartItemsSuccess]);
if (!cartsSuccess || !cartItemsSuccess || !productOffersSuccess) {
return <p>Loading...</p>;
}
...
};
export default ShoppingCart;
Solution
This becomes quite frustrating with around 15 lines of code being utilized for just dependent queries to work. Here the built-in param skip
comes to our rescue and rescues the day.
import { useGetCartsQuery, useGetCartItemsQuery, useGetProductOffersQuery } from './api'; // Replace with your RTK Query API file
const ShoppingCart = () => {
const { data: carts, isSuccess: cartsSuccess } = useGetCartsQuery();
const { data: cartItems, isSuccess: cartItemsSuccess } = useGetCartItemsQuery({cartId: carts[0].id}, skip: !carts);
const { data: productOffers, isSuccess: productOffersSuccess } = useGetProductOffersQuery(
{ productIds: cartItems?.map(item => item.productId) },
{ skip: !cartItems }
);
if (!cartsSuccess || !cartItemsSuccess || !productOffersSuccess) {
return <p>Loading...</p>;
}
...
};
export default ShoppingCart;
The revised code is also dependent on the previous GET
queries and the code is more readable and concise. This not only enhances code clarity but condenses the original 15 lines to 6 lines (3 statements).
React query in my opinion has better documentation and has a better param name for this situation called enabled
.
Conclusion
Streamlining Dependent Queries with skip/enabled
In React production code, managing dependent queries can often lead to an overwhelming number of useEffect
calls, making the codebase less readable and more challenging to maintain. This is especially true when dealing with complex data fetching scenarios, such as those involving multiple API calls dependent on the results of previous queries.
While the RTK Query example offers a concise and declarative syntax, the React Query example emphasizes the importance of the enabled
parameter. Despite the differing terminology, both libraries provide powerful tools for handling dependent queries efficiently.
Subscribe to my newsletter
Read articles from Yatin directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by