Image by Mohamed Hassan from Pixabay
In the first installment of our series on RTK Query and Firebase, we laid the foundation by establishing an API layer and learning about its advantages. In part two, you will learn how to leverage the API layer with RTK Query and gain benefits such as avoiding unnecessary calls to our Firebase database when the data hasn’t changed.
What is RTK query
Similar to React Query, RTK query is a data fetching and caching tool. When you request data with Firestore functions, RTK query is responsible for executing the request, providing the results from the request, and the current state of your request (i.e, isLoading, isError, isSuccess). These request are given via the API layer created in part one of this series. The rest of the series will focus on implementing our Firebase API layer with RTK query so you will gain the benefits of caching and invalidation.
How to implement RTK query
Lets start by examining the variable name menuItemsApiSlice from the code example above. The slice suffix is a naming convention you'll often encounter when working with RTK and RTK query. This convention is employed because it signifies a distinct portion (or slice) of your overall state store. In our current example, it represents the part of our store that deals with menuItems. Setting up a store is beyond the scope of this tutorial. Instead, I'll illustrate how to integrate RTK query using the API Provider near the end of the lesson.
Another crucial aspect to highlight is the createApi function. As its name suggests, this function plays a pivotal role in crafting our API. It takes an object as its argument were each property assumes responsibility for managing your request to a backend server. The details of each property will be explored in the upcoming sections.
baseQuery
The baseQuery property is responsible for setting the root url of all your API request. Because we will be using Firebase, the fakeBaseQuery method is called. This is an alternative that RTK query gives us when we don’t have a traditional url like this: https://pokeapi.co/api/v2/
If we were using a traditional url like the one above, our baseQuery property would accept a fetchBaseQuery method, which accepts an object with the property baseUrl, instead of the fakeBaseQuery.
fetchBaseQuery Example
endpoints
There’s plenty to cover in this code example. Lets take it one step at a time by starting with the endpoints property.
endpoints
This createApi function is responsible for creating your queries that will make calls to a backend server. How does it do this? By using a function (assigned to the endpoints property) that receives a builder object as an argument and returns an object that uses the properties from the builder object (i.e, builder.query) to make queries. All of this is demonstrated in the code above where fetchMenuItems is using the builder.query method to make an api call to our Firestore database. At this point, we have officially connected our api layer created in pt1 of this series, to RTK query. Lets now dive deeper in understanding the builder.query method
builder.query
Currently our fetchMenuItems query is assigned the builder.query method. This method accepts an object as its value and is responsible for making read request to our database. The builder.query method expects two types. The first is a return type, while the second is a type parameter. The former represents the value the function assigned to queryFn should return. The latter represents the value this same function should receive as an argument. If you go back to part one of this series, you will see we’re returning the exact shape our queryFn function expects.
Its also worth mentioning that the builder object also contains a mutation property (i.e, builder.mutation) for making mutations (updates or deletes) to the Firestore database. You will learn later how mutation queries can invalidate data that was previously fetched.
queryFn
In the last section, you learned the return type and type parameter(s) our builder.query expects. Both are managed by the function given to the queryFn property. Because we passed void as the second type to our builder.query method, we don’t pass anything to our queryFn function. However, since we explicitly typed our return type to be an object with the properties payload and lastDocument, we must make sure our data property returns this exact object.
The reason I’m returning an object with the data property is because I’m using queryFn instead of query. If I were using the fetchBaseQuery, mentioned earlier in the tutorial, I would had used the query property and wouldn’t need to return a data object.And if you didn’t return the appropriate object, you might receive the following error message below.
queryFn did not return anything. It needs to return an object with either the shape `{ data: <value> }` or `{ error: <value> }`
tagTypes, providesTags, invalidatesTags
So far you have the knowledge and skills and to read data from a Firestore database with RTK query via an API layer. But what happens if you decided to make a mutation to data that was previously read from the database? How does RTK query handle this scenario? The answer is “tags”.
In the code example above, you will notice our code introduces the tagTypes and providesTags properties. The tagTypes property allows you the ability to invalidate data. And the provideTags properties allows you to provide tags to a specific query so it will respond to invalidation. Lets break this down with the following example:
Lets say you mutate your Firestore database and this effects data that was previously read from your database. Because you created a tag (MenuItems) via tagTypes and provided that tag via providesTags to fetchMenuItems, fetchMenuItems would refetch the new data. How can you make sure this process happens? By adding an invalidatesTags with the same value as the providesTags property to a mutation query. Below is an example of a query mutation that would trigger fetchMenuItems to get called again.
How to use queries with custom hooks
Since our queries are established, we can implement them in our code base via hooks. RTK will automatically generate this hooks from the createApi method if you import them from @reduxjs/toolkit/query/react. Below is an example of the custom hook RTK query will automatically create behind the scenes for us.
Here’s an example of the hook being used in a React component. Notice it provides the status and results of our network request with the following values: data, isError, isLoading. Refetch is used to force a refetch. I’ve used this feature in a React Native project when the users internet is disconnected and they need to refresh the screen.
How to use RTK query with an API provider
We have our API Layer integrated with RTK query and we have custom hooks that allow us to make API request in our React components. However, none of the functionality we’ve implemented will be useful until we incorporate an API Provider (or a store if you choose) with our menuItemsApiSlice as an api value in our App.tsx file.
This concludes today lesson. I hope you learned something new. If you have questions regarding this article, feel free to reach out to me on LinkedIn.