Dung (Donny) Nguyen

Senior Software Engineer

RTK Query

RTK Query is a powerful data-fetching and caching tool built into Redux Toolkit that eliminates the boilerplate code typically required for managing server state in React applications.

🎯 The Problem It Solves

Without RTK Query, managing server data in Redux requires:

This results in hundreds of lines of repetitive code for even simple CRUD operations.

✨ How RTK Query Solves These Problems

1. Fetching Made Simple

2. Intelligent Caching

3. Seamless Data Syncing

4. Built-in State Management

🧠 Core Concepts

API Slice: Define all your endpoints in one place

Endpoints: Define queries (GET) and mutations (POST, PUT, DELETE)

Cache Management: Automatic with manual control when needed

🛠️ Example Setup

import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';

export const apiSlice = createApi({
  reducerPath: 'api',
  baseQuery: fetchBaseQuery({ baseUrl: '/api' }),
  tagTypes: ['Post'], // Define cache tags
  endpoints: (builder) => ({
    // Query: Fetches and caches data
    getPosts: builder.query<Post[], void>({
      query: () => '/posts',
      providesTags: ['Post'], // Cache tagged as 'Post'
    }),
    // Mutation: Updates server and invalidates cache
    addPost: builder.mutation<Post, Partial<Post>>({
      query: (newPost) => ({
        url: '/posts',
        method: 'POST',
        body: newPost,
      }),
      invalidatesTags: ['Post'], // Refetch posts after adding
    }),
  }),
});

export const { useGetPostsQuery, useAddPostMutation } = apiSlice;

Using in a component:

function PostsList() {
  const { data: posts, isLoading, isError } = useGetPostsQuery();
  const [addPost, { isLoading: isAdding }] = useAddPostMutation();

  if (isLoading) return <div>Loading...</div>;
  if (isError) return <div>Error loading posts</div>;

  return (
    <div>
      {posts?.map(post => <div key={post.id}>{post.title}</div>)}
      <button onClick={() => addPost({ title: 'New Post' })}>
        {isAdding ? 'Adding...' : 'Add Post'}
      </button>
    </div>
  );
}

🧩 Advanced Features

Optimistic Updates: Update UI immediately, rollback if server fails

addPost: builder.mutation({
  async onQueryStarted(newPost, { dispatch, queryFulfilled }) {
    const patchResult = dispatch(
      apiSlice.util.updateQueryData('getPosts', undefined, (draft) => {
        draft.push(newPost);
      })
    );
    try {
      await queryFulfilled;
    } catch {
      patchResult.undo(); // Rollback on error
    }
  },
})

Polling: Keep data fresh with automatic refetching

const { data } = useGetPostsQuery(undefined, {
  pollingInterval: 3000, // Refetch every 3 seconds
});

Conditional Fetching: Skip unnecessary requests

const { data } = useGetPostQuery(postId, {
  skip: !postId, // Don't fetch if no ID
});

💡 Key Takeaway

RTK Query transforms server state management from a tedious, error-prone process into a declarative, maintainable solution. By handling fetching, caching, and synchronization automatically, it lets you focus on building features instead of managing data flow infrastructure.