DEV Community

Cover image for Complete redux toolkit (Part - 4)
Abhishek Panwar
Abhishek Panwar

Posted on • Edited on

Complete redux toolkit (Part - 4)

Part 4: Advanced Topics in RTK Query.

This part will focus on advanced features and use cases in RTK Query, including customising queries, handling authentication, optimistic updates, and performance optimisation.

Part 4: Advanced Topics in RTK Query

1. Introduction to Advanced RTK Query Concepts

In the previous part, we covered the basics of using RTK Query for fetching and mutating data. Now, we will dive into more advanced features that make RTK Query even more powerful. These features allow you to customize queries, manage authentication, optimize performance, and handle optimistic updates for a smoother user experience.

2. Customizing baseQuery for Authentication

When working with APIs that require authentication, you need to customize the baseQuery to include authentication headers like JWT tokens or API keys.

Step 1: Create a Custom baseQuery

You can create a custom baseQuery function that adds authorization headers to every request.

// src/app/customBaseQuery.js
import { fetchBaseQuery } from '@reduxjs/toolkit/query/react';

const customBaseQuery = fetchBaseQuery({
  baseUrl: 'https://jsonplaceholder.typicode.com/',
  prepareHeaders: (headers, { getState }) => {
    const token = getState().auth.token; // Assuming auth slice has token
    if (token) {
      headers.set('Authorization', `Bearer ${token}`);
    }
    return headers;
  },
});

export default customBaseQuery;
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • prepareHeaders: This function allows you to customize headers for each request. It retrieves the token from the Redux store and attaches it to the Authorization header.

Step 2: Use the Custom baseQuery in createApi

Modify your postsApi.js file to use the custom baseQuery:

// src/features/posts/postsApi.js
import { createApi } from '@reduxjs/toolkit/query/react';
import customBaseQuery from '../../app/customBaseQuery';

export const postsApi = createApi({
  reducerPath: 'postsApi',
  baseQuery: customBaseQuery, // Use the custom base query here
  tagTypes: ['Post'],
  endpoints: (builder) => ({
    fetchPosts: builder.query({
      query: () => 'posts',
      providesTags: (result) =>
        result ? result.map(({ id }) => ({ type: 'Post', id })) : ['Post'],
    }),
    addPost: builder.mutation({
      query: (newPost) => ({
        url: 'posts',
        method: 'POST',
        body: newPost,
      }),
      invalidatesTags: ['Post'],
    }),
  }),
});

export const { useFetchPostsQuery, useAddPostMutation } = postsApi;
Enter fullscreen mode Exit fullscreen mode

3. Optimistic Updates with RTK Query

Optimistic updates allow you to immediately update the UI before the server confirms the mutation, providing a smoother user experience. If the server returns an error, the UI can revert to the previous state.

Step 1: Implement Optimistic Updates in Mutations

You can implement optimistic updates using the onQueryStarted lifecycle method provided by RTK Query.

// src/features/posts/postsApi.js
addPost: builder.mutation({
  query: (newPost) => ({
    url: 'posts',
    method: 'POST',
    body: newPost,
  }),
  invalidatesTags: ['Post'],
  onQueryStarted: async (newPost, { dispatch, queryFulfilled }) => {
    // Optimistic update: immediately add the new post to the cache
    const patchResult = dispatch(
      postsApi.util.updateQueryData('fetchPosts', undefined, (draftPosts) => {
        draftPosts.push({ id: Date.now(), ...newPost }); // Fake ID for optimistic update
      })
    );
    try {
      await queryFulfilled; // Await server response
    } catch {
      patchResult.undo(); // Revert if the mutation fails
    }
  },
}),
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • onQueryStarted: This lifecycle method is triggered when a mutation starts. It provides dispatch and queryFulfilled parameters to manage cache updates.
  • postsApi.util.updateQueryData: This utility function allows you to optimistically update cached data.
  • patchResult.undo(): Reverts the optimistic update if the server returns an error.

4. Handling Dependent Queries

Sometimes, you may need to perform dependent queries, where one query depends on the result of another. RTK Query provides the skip parameter to control when a query is executed.

Example: Fetch Post Details Based on Selected Post ID

// src/features/posts/PostDetails.js
import React from 'react';
import { useFetchPostQuery } from './postsApi';

const PostDetails = ({ postId }) => {
  const { data: post, error, isLoading } = useFetchPostQuery(postId, { skip: !postId });

  if (!postId) return <p>Select a post to view details.</p>;
  if (isLoading) return <p>Loading...</p>;
  if (error) return <p>Error loading post details.</p>;

  return (
    <div>
      <h3>{post.title}</h3>
      <p>{post.body}</p>
    </div>
  );
};

export default PostDetails;
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • useFetchPostQuery: A query hook that takes postId as an argument. If postId is not provided, the query is skipped using { skip: !postId }.

5. Polling and Real-Time Data with RTK Query

RTK Query supports polling to keep data fresh at a specified interval. This is useful for real-time data synchronization.

Step 1: Use Polling in Queries

You can enable polling for any query using the pollingInterval option.

// src/features/posts/PostsList.js
import React from 'react';
import { useFetchPostsQuery } from './postsApi';

const PostsList = () => {
  const { data: posts, error, isLoading } = useFetchPostsQuery(undefined, {
    pollingInterval: 30000, // Poll every 30 seconds
  });

  if (isLoading) return <p>Loading...</p>;
  if (error) return <p>An error occurred: {error.message}</p>;

  return (
    <section>
      <h2>Posts</h2>
      <ul>
        {posts.map((post) => (
          <li key={post.id}>{post.title}</li>
        ))}
      </ul>
    </section>
  );
};

export default PostsList;
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • pollingInterval: This option specifies the interval (in milliseconds) at which the query should poll the server for new data.

6. Optimizing Performance with selectFromResult

RTK Query provides the selectFromResult option for advanced performance optimizations by allowing you to select specific data from the query result.

Step 1: Using selectFromResult to Optimize Re-Renders

The selectFromResult option can be used to prevent unnecessary re-renders when only a subset of the query result is needed.

// src/features/posts/PostTitleList.js
import React from 'react';
import { useFetchPostsQuery } from './postsApi';

const PostTitleList = () => {
  const { data: posts } = useFetchPostsQuery(undefined, {
    selectFromResult: ({ data }) => ({ titles: data?.map((post) => post.title) }),
  });

  return (
    <section>
      <h2>Post Titles</h2>
      <ul>
        {posts?.map((title, index) => (
          <li key={index}>{title}</li>
        ))}
      </ul>
    </section>
  );
};

export default PostTitleList;
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • selectFromResult: This option allows you to select only the titles from the fetched posts, preventing unnecessary re-renders when other data in the query result changes.

7. Conclusion and Next Steps

In this part, we explored advanced topics in RTK Query, such as customizing baseQuery for authentication, handling optimistic updates, managing dependent queries, using polling for real-time data synchronization, and optimizing performance with selectFromResult. RTK Query's rich feature set makes it a powerful tool for handling data fetching and caching in modern Redux applications.

In the next part, we'll discuss Testing Strategies for Redux Toolkit and RTK Query, covering unit testing, integration testing, and best practices for ensuring robust and maintainable code.

Stay tuned for Part 5: Testing Strategies for Redux Toolkit and RTK Query!

Top comments (0)