Making the Leap: Why Switching to RTK Query from Saga Makes Sense

8 mins read

Introduction:

In the ever-changing world of web development, developers are always on the lookout for tools and technologies that can help them streamline their workflows, improve performance, and improve the user experience.
Redux Saga and RTK Query have emerged as popular options as data fetching solutions, AKA Server State Management, in React applications. While both have advantages, this blog post will explain why switching from Redux Saga to RTK Query may be a good idea for your project.

First and foremost, I want to applaud the Redux-Saga for providing the most comprehensive method for dealing with asynchronous tasks.

Points of Comparison

Simplified Data Fetching

Redux Saga is well-known for its ability to handle complex asynchronous operations. However, it frequently entails writing verbose code, which can be difficult to manage as your application grows.

RTK Query, on the other hand, abstracts away much of the boilerplate code, resulting in a simpler and more intuitive data fetching API. As a result, the code is cleaner and more readable, making it easier to maintain.

One of the best features of RTKQuery is that it automatically generates a type-safe react hook for letting you handle your data fetching inside your component, which gives you the loading state, the response data, the error state, and much more out of the box

Reduced Boilerplate

The amount of boilerplate code required to handle asynchronous actions and side effects is one of the most common complaints about Redux Saga.

This can result in a steeper learning curve for new developers joining the project and an increase in the likelihood of bugs being introduced.

The caching, normalization, and handling of loading and error states built into RTK Query significantly reduce the need for manual configuration and boilerplate, resulting in faster development and fewer errors.

RTK Query significantly reduces boilerplate code compared to using Redux Saga for data fetching in React applications. While the exact percentage reduction can vary depending on the specific use case, it is not uncommon to see a reduction of around 70-90% in code volume when using RTK Query.

If you already use Redux Toolkit, that is great because RTKQuery is included within it and you do not need to install any additional package.
Let’s compare the boilerplate code for Redux Saga and RTK Query in a simple data fetching example, both utilizing the Redux Toolkit.

Redux-Saga
Create a Redux slice file

dataSlice.js


import { createSlice } from '@reduxjs/toolkit';
const dataSlice = createSlice({
    name: 'data',
    initialState: {
        loading: false,
        data: null,
        error: null,
    },
    reducers: {
        fetchData: (state) = & gt;
        {
            state.loading = true;
            state.data = null;
            state.error = null;
        },
        fetchDataSuccess: (state, action) = & gt;
        {
            state.loading = false;
            state.data = action.payload;
        },
        fetchDataFailure: (state, action) = & gt;
        {
            state.loading = false;
            state.error = action.payload;
        },
    },
});

export const {
    fetchData,
    fetchDataSuccess,
    fetchDataFailure
} = dataSlice.actions;
export default dataSlice.reducer; 

Create a Saga file
dataSaga.js



import { takeLatest, put, call } from 'redux-saga/effects';
import { fetchDataSuccess, fetchDataFailure } from './dataSlice';
import { fetchDataFromApi } from './api';

function* fetchDataSaga() {
    try {
        const data = yield call(fetchDataFromApi);
        yield put(fetchDataSuccess(data));
    } catch (error) {
        yield put(fetchDataFailure(error.message));
    }
}

export function* watchFetchData() {
    yield takeLatest('data/fetchData', fetchDataSaga);
}


Write a selector

dataSelectors.js



import {
    createSelector
} from 'reselect';

const selectDataState = (state) = & gt;
state.data;

export const selectLoading = createSelector(
    selectDataState,
    (data) = & gt; data.loading
);

export const selectData = createSelector(
    selectDataState,
    (data) = & gt; data.data
);

export const selectError = createSelector(
    selectDataState,
    (data) = & gt; data.error
);


Usage in your component



import React from 'react';
import {
    useSelector
} from 'react-redux';
import {
    selectLoading,
    selectData,
    selectError
} from './dataSelectors';

const DataComponent = () = & gt;
{
    const loading = useSelector(selectLoading);
    const data = useSelector(selectData);
    const error = useSelector(selectError);

    useEffect(() => {
        dispatch(fetchData());
    }, [dispatch]);

    if (loading) {
        return 
Loading...
; } if (error) { return
Error: { error }
; } return
Data: { data }
; }; export default DataComponent;

B. RTK-Query
Create an Api Service

apiSlice.js



import { createApi, fetchBaseQuery } from '@rtk-incubator/rtk-query';
import {
    createApi,
    fetchBaseQuery
} from '@rtk-incubator/rtk-query';

const baseQuery = fetchBaseQuery({
    baseUrl: '/api'
});

export const api = createApi({
    baseQuery,
    endpoints: (builder) = & gt;
    ({
        fetchData: builder.query({
            query: () = & gt;
            'data',
        }),
    }),
});

export const {
    useFetchDataQuery
} = api;

Usage in your component


import React from 'react';
import { useFetchDataQuery } from './apiSlice';

const DataComponent = () => {
const { data, error, isLoading } = useFetchDataQuery();

if (isLoading) {
return
Loading...
; } if (error) { return
Error: {error.message}
; } return
Data: {data}
; }; export default DataComponent;

The data fetching logic in this RTK Query example is handled by the useFetchDataQuery hook provided by RTK Query.

It automatically manages the loading state, error handling, and caching of the data. Overall, the RTK Query example requires less boilerplate code compared to Redux Saga.

RTK Query abstracts away many of the manual steps involved in handling data fetching, caching, and state management, resulting in a cleaner and more concise codebase.

Automatic Caching and Invalidation:

RTK Query brings automatic caching and data invalidation to the table, which can drastically improve the performance of your application.

It intelligently caches fetched data and automatically updates the cache when data changes, eliminating the need for developers to write custom caching logic.

This feature not only enhances the user experience by displaying up-to-date information but also reduces the number of unnecessary network requests.
Type Safety and Developer Experience:

RTK Query is built with TypeScript in mind, providing strong type safety and autocompletion support out of the box. This leads to fewer type-related errors and improved developer productivity.
While TypeScript can also be used with Redux Saga, RTK Query’s integration and focus on type safety make it a more straightforward choice for developers looking to adopt TypeScript in their projects.
Code Spliting

RTK Query makes it possible to trim down your initial bundle size by allowing you to inject additional endpoints after you’ve set up your initial service definition. This can be very beneficial for larger applications that may have many endpoints.

In the case of Redux Sage, you need an additional package to do it for you, but RTKQuery supports it out of the box.

Example.

A typical approach would be to have one empty central API slice definition:


// Or from '@reduxjs/toolkit/query' if not using the auto-generated hooks
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'

// initialize an empty api service that we'll inject endpoints into later as needed
export const emptySplitApi = createApi({
baseQuery: fetchBaseQuery({ baseUrl: '/' }),
endpoints: () => ({}),
})

and then inject the API endpoints in other files and export them from there – that way, you will be sure to always import the endpoints in a way that they are definitely injected.


import { emptySplitApi } from './emptySplitApi'

const extendedApi = emptySplitApi.injectEndpoints({
endpoints: (build) => ({
example: build.query({
query: () => 'test',
}),
}),
overrideExisting: false,
})

export const { useExampleQuery } = extendedApi

Code Genration

RTKQuery can generate hooks and types based on the api end point you define, Both for REST and Graphql Api,

For REST-API it can also be generated from the OpenAPI Schema definition file.
Prefetching
The goal of prefetching is to make data fetch before the user navigates to a page or attempts to load some known content. This way, you can prevent fetching waterfalls.

Example usecase

  • The user hovers over a navigation element
  • The user hovers over a list element that is a link
  • The user hovers over the next pagination button
  • The user navigates to a page and you know that some components down the tree will require said data etc…

Out of the box, RTKQ gives you a hook called usePrefetch to prefetch your data.

For Redux Saga, you should define a separate action and state to manage it.
8. Polling
Polling gives you the ability to have a realtime effect by causing a query to run at a set of interval.

In redux saga Implementing polling is also add an additional boiler plate in your code base and a headache for a developer,

9. Streaming Updates
Streaming updates can be used to enable the API to receive real-time updates to the back-end data, such as new entries being created, or important properties being updated.

Examples of use cases that benefit from streaming updates are:
GraphQL subscriptions
Real-time chat applications
Real-time multiplayer games
Collaborative document editing with multiple concurrent users

onCacheEntryAdded function is used to work with stream data, and you can use it to handle the way to update the query when stream data is received.
Personal Experiance
In my experience with RTKQuery, I found it more developer friendly, reducing unnecessary network requests and optimistic updates, and also having the ability to work with multiple dependent data sets, meaning that if one changes, the other should be re-validated to show the latest data.

Conclusion:

Switching from Redux Saga to RTK Query can bring a plethora of benefits to your React application development process. With simplified data fetching, reduced boilerplate, automatic caching, integrated state management, and an enhanced developer experience, RTK Query offers a compelling alternative for managing asynchronous operations.

While Redux Saga remains a solid option for certain use cases, RTK Query’s modern approach and features make it a strong contender for teams looking to optimize their workflows and create more efficient and maintainable applications.

As the web development landscape continues to evolve, it’s important to embrace tools that can keep pace and empower developers to deliver top-notch user experiences.