Why I put useEffect in the bin

When the React team released hooks in early 2019, many developers were scratching their heads. The reason behind hooks was hard for some to understand. At that time I was working on a project with a Microsoft backend and a TypeScript/React front-end and we were using Redux to handle all the state management.

Hooks weren't really accepted in the team I was in. We already had most of the project complete and to add a new dimension into it which some didn't really want wasn't going to go down well. Thankfully it wasn't long till I picked up my new project and I was there to start the frontend from scratch.

That's where I could do what I like, but one of the biggest headaches was how the rendering would affect hooks and especially useEffect. Relying on the observed dependancies meant that we would have quite a few issues with multiple calls at times we didn't even want them to happen. But the biggest issue came when another developer made a crucial error with the useEffect hook.

That error was that they were making a call to an endpoint, but when they wrote up the code for it, they followed their IDEs suggestion to remove the dependancies array as it was empty and wasn't being used. This caused the call to happen repeatedly, and they weren't aware it was happening. About 30 minutes later we got a call from the DevOps team to say that there were hundreds of thousands of calls happening.

    useEffect(() ->{
	    const data = useProductList(categoryId);
	    setProducts(data);
    });

We quickly found the issue, and fixed it but it still felt like the way we were using useEffect for our API calls was archaic. That's when we found ReactQuery (now TanStack Query so we'll call it that from now on).

TanStack Query is a library dedicated to state management, server-state management and data fetching. The reason it's now TanStack Query is because not only supports React but also SolidJS, Vue and Svelte. The benefit to using it over useEffect for us was that it not only allowed a really readable code trace, but it also allowed us a really granular way to manage all those requests with the cache options.

So how do we get started with TanStack Query? Well the first thing to do is to get the QueryClientProvider setup. It sits at the top of the app stack and it manages all the calls and the cache for the whole app.

    <QueryClientProvider client={queryClient}>
    	<Component {...pageProps} />
    </QueryClientProvider>

When you make a query it records it and tracks it's status. When you change something, it can manage the cache and recall any query that has been modified. When a cache times out it will call it again if it's in scope.

To make a basic query is very simple.

const {data} = useQuery('product-list', getProducts);

This will save the data call with the key 'product-list' in the cache and assign it a default timeout. If the call happens again before the cache expires, it'll come straight from the cache, saving you a call to the backend and speeding up your application.

You can specify this in more detail using the options variable, and get more back from the query.

const {data:products, isLoading, error} = useQuery({
									    queryKey: 'product-list',
									    queryFn: getProducts
								    });

From there you can now have some control over your UI when the fetch is happening. With the isLoading you can show your users that the data is being fetched, and when isLoading is false you can show your data or act on errors returned.

TanStack Query is also great for CRUD operations. Using the mutate operator you can add a record to your backend.

const mutation = useMutate({
    mutationFn: (newProduct) => {
	    return fetch('/api/addProduct/', product);
    },
    onSuccess: (data:addedProduct) => {
	    queryClient.setQueryData(['product-', addedProduct.id], addedProduct);
	    navigate(`/product/${addedProduct.id}`};
    }
});

Here we add a new product, and the onSuccess returns the data we passed. We then add this product to our cache before we navigate to the individual product page. When we get to the product page TanStack populates the data from the cache that we just added and we save a call to the backend.

So reading and writing with TanStack is a very refreshing experience. Everything is managed for you, your app is speedy and you're not going to be trying to explain to your client/boss why there were half a million calls to the API in a short space or time.

There are also lots of other functionalities in the TanStack library. I'll write more about them in the future but until then you should checkout TanStack and see what it's all about. https://tanstack.com/query/

All articles are my own thoughts. Gathered from reading articles and trying things out.