Understanding RTK Query: A Real-World Implementation in React Native Application

Musango WopeMusango Wope
5 min read

Introduction

I've been developing a React Native app called Geomusic that allows users to pin Spotify playlists to geographical locations. For efficient data fetching from Spotify's API, I chose RTK Query—a powerful data fetching and caching solution built on Redux Toolkit that eliminates the need to write data fetching logic by hand.

What is RTK Query?

RTK Query provides:

  • Automatic caching with intelligent cache invalidation

  • Background refetching capabilities

  • Error handling and loading states

  • TypeScript support out of the box

  • Minimal boilerplate code

The Implementation

Here's my complete Spotify playlist query implementation with detailed comments:

import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { TOKEN_STORAGE_KEY } from '@/features/authentication/providers/SpotifyAuthProvider';

export const spotifyPlaylistsQuery = createApi({
  // Unique key for this API slice in the Redux store
  reducerPath: 'spotifyPlaylistsQuery',

  // Configure the base query with Spotify API settings
  baseQuery: fetchBaseQuery({
    // Base URL for all Spotify API endpoints
    baseUrl: 'https://api.spotify.com/v1/',

    // Function that runs before every request to add headers
    prepareHeaders: async (headers) => {
      // Retrieve stored Spotify access token from AsyncStorage
      const token = await AsyncStorage.getItem(TOKEN_STORAGE_KEY);

      if (token) {
        // Add Bearer token for authentication
        headers.set('Authorization', `Bearer ${token}`);
        // Set content type for JSON requests
        headers.set('Content-Type', 'application/json');
      }

      return headers;
    },
  }),

  // Define the API endpoints
  endpoints: (builder) => ({
    // Endpoint to fetch user's playlists
    getPlaylists: builder.query<PlaylistItem[], void>({
      // API endpoint path (gets appended to baseUrl)
      query: () => 'me/playlists',
      // Transform the response to extract only the items array
      transformResponse: (response: SpotifyPlaylistsResponse) => response.items,
    }),

    // Endpoint to fetch a specific playlist by ID
    getPlaylistById: builder.query<SpotifyPlaylistDetailResponse, string>({
      // Dynamic endpoint path using the playlistId parameter
      query: (playlistId) => `playlists/${playlistId}`,
    }),
  }),
});

// Export auto-generated hooks for use in components
export const { useGetPlaylistsQuery, useGetPlaylistByIdQuery } = spotifyPlaylistsQuery;

Store Setup

import { configureStore } from '@reduxjs/toolkit';
import { setupListeners } from '@reduxjs/toolkit/query';
import { spotifyPlaylistsQuery } from '@/redux/queries/spotifyPlaylistQuery';

export const store = configureStore({
  reducer: {
    // Add the API slice reducer to the store
    // The key must match the reducerPath from the API definition
    [spotifyPlaylistsQuery.reducerPath]: spotifyPlaylistsQuery.reducer,
    // Add other reducers here as needed
    // geoPlaylists: playlistsReducer,
  },

  // Add RTK Query middleware to enable caching, invalidation, polling, etc.
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware({
      // Configure serializable check to ignore RTK Query actions
      serializableCheck: {
        ignoredActions: ['persist/PERSIST', 'persist/REHYDRATE'],
        ignoredPaths: [`${spotifyPlaylistsQuery.reducerPath}.queries`],
      },
    }).concat(spotifyPlaylistsQuery.middleware), // Add the API middleware
});

// Enable useful features like refetchOnFocus and refetchOnReconnect
setupListeners(store.dispatch);

// Export types for TypeScript usage
export type AppDispatch = typeof store.dispatch;
export type RootState = ReturnType<typeof store.getState>;

Provider Setup

To use RTK Query in your React Native app, wrap your app with the Redux Provider:

import React from 'react';
import { Provider } from 'react-redux';
import { store } from '@/redux/store';
import { Stack } from 'expo-router';

export default function RootLayout() {
  return (
    // Provide the Redux store to all child components
    <Provider store={store}>
      <Stack>
        <Stack.Screen name="playlists" options={{ headerShown: false }} />
        {/* Add other screens here */}
      </Stack>
    </Provider>
  );
}

The Provider component makes the Redux store (and RTK Query) available to all components in your app, enabling them to use the generated hooks like useGetPlaylistsQuery.

Real-World Usage

Here's how RTK Query is used in the actual PlaylistsScreen component:

import React from 'react';
import { SafeAreaView } from 'react-native';
import { useGetPlaylistsQuery } from '@/redux/queries/spotifyPlaylistQuery';

const PlaylistsScreen = () => {
  const { error, data, isFetching, isLoading } = useGetPlaylistsQuery();

  if (isFetching || isLoading) {
    return (
      <SafeAreaView>
        <ThemedText>Loading</ThemedText>
      </SafeAreaView>
    );
  }

  return (
    <SafeAreaView>
      <Header headerText="Select a playlist to pin" />
      <Playlists playlists={data} />
    </SafeAreaView>
  );
};

Key Benefits

1. Automatic Caching and Performance

When users browse different playlists, RTK Query automatically caches results. Returning to the PlaylistsScreen loads data instantly from cache rather than making another API call.

2. Built-in Loading States

The app gracefully handles loading states while data is being fetched, with automatic state management.

3. Background Refetching

RTK Query automatically refetches data when:

  • The app regains focus

  • Network connection is restored

  • Cache becomes stale

Caching in Action

First Request:

  1. Component calls useGetPlaylistByIdQuery('playlist-id')

  2. RTK Query checks cache - no data found

  3. Makes network request to Spotify API

  4. Stores response in cache

Second Request (same ID):

  1. Component calls useGetPlaylistByIdQuery('playlist-id') again

  2. RTK Query finds cached data

  3. No network request - returns cached data immediately

This automatic caching makes the app faster and reduces API calls, which is crucial for rate-limited APIs like Spotify's.

Before vs After RTK Query

Before (Traditional Approach):

const [playlists, setPlaylists] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);

const fetchPlaylists = async () => {
  setLoading(true);
  try {
    const token = await AsyncStorage.getItem(TOKEN_STORAGE_KEY);
    const response = await fetch('https://api.spotify.com/v1/me/playlists', {
      headers: { Authorization: `Bearer ${token}` }
    });
    const data = await response.json();
    setPlaylists(data.items);
  } catch (err) {
    setError(err);
  } finally {
    setLoading(false);
  }
};

After RTK Query:

const { error, data, isFetching, isLoading } = useGetPlaylistsQuery();

Conclusion

RTK Query transformed Geomusic's data fetching from complex manual state management to simple, declarative hooks. The results:

  • Automatic caching - instant loading for previously fetched data

  • Built-in loading states - no more manual loading/error state management

  • Better performance - request deduplication and background refetching

  • TypeScript integration - full type safety out of the box

For any React Native app that consumes APIs, RTK Query eliminates the tedious parts of data fetching while delivering enterprise-grade features automatically.

0
Subscribe to my newsletter

Read articles from Musango Wope directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Musango Wope
Musango Wope