In this module, you'll learn about RTK Query, a powerful data fetching and caching tool built into Redux Toolkit. You'll explore how to create and use queries, handle mutations, and effectively manage server state in your applications.
RTK Query is a powerful data fetching and caching solution that is part of the Redux Toolkit. It provides a streamlined API to simplify asynchronous data fetching, caching, and state management in React applications.
RTK Query offers several advantages that make it an excellent choice for managing server state:
RTK Query acts as a middleware between your React application and your backend APIs, replacing manual fetch calls and state management with hooks that handle the entire lifecycle of API calls.
The first step in using RTK Query is to create an API slice using the createApi
function. This slice will define how your application interacts with your API.
// usersApi.js import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react' // The usersApi slice - handling API interactions for user data export const usersApi = createApi({ // Unique key that defines where the data will be stored in Redux state reducerPath: 'usersApi', // Setting up the base query with the base URL pointing to the backend service baseQuery: fetchBaseQuery({ baseUrl: 'http://localhost:9009/api/' }), // Endpoints will be defined here in future steps endpoints: (builder) => ({}) });
Once you've created your API slice, you need to integrate it with your Redux store:
// store.js import { configureStore } from '@reduxjs/toolkit' import { usersApi } from './usersApi' // Configuring the Redux store, adding our API slice and middleware export const store = configureStore({ // The root reducer object where we mount our slice reducers reducer: { // Mounting the API reducer under a dynamic key [usersApi.reducerPath]: usersApi.reducer, }, // Customizing the store's middleware chain middleware: (getDefaultMiddleware) => // Adding the API middleware to the default middleware chain getDefaultMiddleware().concat(usersApi.middleware), });
This setup injects RTK Query's capabilities into your Redux store, allowing it to manage server state alongside your client state.
After setting up the API service, you need to define endpoints that correspond to API operations:
// usersApi.js (continued from above) export const usersApi = createApi({ reducerPath: 'usersApi', baseQuery: fetchBaseQuery({ baseUrl: 'http://localhost:9009/api/' }), endpoints: (builder) => ({ // Query endpoint to fetch all users getUsers: builder.query({ query: () => 'users', }), // Query endpoint to fetch a single user by ID getUserById: builder.query({ query: (userId) => `users/${userId}`, }), }), }); // Export the auto-generated hooks export const { useGetUsersQuery, useGetUserByIdQuery } = usersApi;
RTK Query auto-generates hooks for each endpoint that you can use directly in your components:
// UsersList.js import React from 'react'; import { useGetUsersQuery } from './usersApi'; function UsersList() { // The hook provides data and loading/error states const { data: users, isLoading, isError, error } = useGetUsersQuery(); if (isLoading) { return <div>Loading users...</div>; } if (isError) { return <div>Error loading users: {error.message}</div>; } return ( <div> <h2>Users</h2> <ul> {users.map(user => ( <li key={user.id}>{user.name}</li> ))} </ul> </div> ); }
RTK Query uses tags to manage cache invalidation. Tags allow you to mark which queries should be refetched when mutations occur:
// usersApi.js with tags export const usersApi = createApi({ reducerPath: 'usersApi', baseQuery: fetchBaseQuery({ baseUrl: 'http://localhost:9009/api/' }), tagTypes: ['User'], // Define tag types endpoints: (builder) => ({ getUsers: builder.query({ query: () => 'users', // Provide tags to this query providesTags: ['User'], }), getUserById: builder.query({ query: (userId) => `users/${userId}`, // Provide a specific tag for each user providesTags: (result, error, userId) => [{ type: 'User', id: userId }], }), // Mutation to add a new user addUser: builder.mutation({ query: (newUser) => ({ url: 'users', method: 'POST', body: newUser, }), // Invalidate the User tag to trigger refetching invalidatesTags: ['User'], }), }), }); export const { useGetUsersQuery, useGetUserByIdQuery, useAddUserMutation } = usersApi;
With this setup, whenever the addUser
mutation is executed, any query with the User
tag will be automatically refetched, ensuring your UI stays in sync with the server data.
RTK Query provides comprehensive request state management, allowing you to easily handle loading, success, and error states in your UI:
// UserForm.js import React, { useState } from 'react'; import { useAddUserMutation } from './usersApi'; function UserForm() { const [name, setName] = useState(''); const [email, setEmail] = useState(''); // The mutation hook returns an array with the trigger function and result object const [addUser, { isLoading, isSuccess, isError, error }] = useAddUserMutation(); const handleSubmit = async (e) => { e.preventDefault(); try { // Trigger the mutation await addUser({ name, email }); // Clear form on success setName(''); setEmail(''); } catch (err) { // Error is handled by RTK Query console.error('Failed to add user:', err); } }; return ( <div> <h2>Add New User</h2> {isSuccess && <p className="success">User added successfully!</p>} {isError && <p className="error">Error: {error.message}</p>} <form onSubmit={handleSubmit}> <div> <label htmlFor="name">Name:</label> <input id="name" value={name} onChange={(e) => setName(e.target.value)} disabled={isLoading} /> </div> <div> <label htmlFor="email">Email:</label> <input id="email" value={email} onChange={(e) => setEmail(e.target.value)} disabled={isLoading} /> </div> <button type="submit" disabled={isLoading}> {isLoading ? 'Adding...' : 'Add User'} </button> </form> </div> ); }
RTK Query also provides additional properties like isFetching
(for subsequent requests), refetch
(to manually trigger refetching), and data
(the cached response), making it a complete solution for API state management.