CodeWithYou

Mastering Data Fetching: Integrating RTK Query with Redux Toolkit in Your React Movie App

Published on
Authors
"Mastering Data Fetching: Integrating RTK Query with Redux Toolkit in Your React Movie App"
Photo by AI

Mastering Data Fetching: Integrating RTK Query with Redux Toolkit in Your React Movie App

In the modern web development landscape, efficient state management is crucial for building responsive and dynamic applications. One of the most popular state management libraries is Redux, designed to create applications that behave predictably across different environments, such as servers and native applications. The Redux Toolkit is the official way to write Redux logic, created to simplify development while minimizing boilerplate code and configurations.

However, the complexity of integrating APIs with Redux often leads to cumbersome setups. To address this, RTK Query was introduced as an optional add-on to the Redux Toolkit, aimed at making data fetching and caching easier. In this post, we'll explore how to seamlessly integrate RTK Query with Redux Toolkit by building a simple asynchronous CRUD application: a Movie app.

Table of Contents

Prerequisites

Before diving in, ensure you have a solid understanding of React and basic familiarity with Redux. This guide assumes you're comfortable with TypeScript, although you can adapt it if you're using plain JavaScript.

Understanding RTK Query and Core Concepts

At the heart of RTK Query lies the createApi function. This function enables you to define an API slice that specifies the server's base URL alongside a collection of endpoints to fetch and manipulate data. Each defined endpoint generates a custom hook tailored for use within your React components, allowing for dynamic content rendering based on the status of API requests.

Here's a simple example of how to create an API slice using createApi:

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

export const apiSlice = createApi({
    reducerPath: 'api',
    baseQuery: fetchBaseQuery({ baseUrl: 'https://server.co/api/v1/'}),
    endpoints: (builder) => ({
        getData: builder.query({
            query: () => '/data',
        })
    })
});

export const { useGetDataQuery } = apiSlice;

In this code snippet, fetchBaseQuery is a lightweight wrapper around the native fetch function, easing the process of API requests.

Integrating RTK Query with Redux Toolkit

Let’s build our Movie app, where users can view, add, update, and delete movies from a mock backend. We will use TypeScript in this tutorial.

Setting up Your Development Environment

  1. Create a new React project:
    Run the command:

    npm create vite@latest
    

    Follow the prompts to set up your project.

  2. Install dependencies:
    Install @reduxjs/toolkit and react-redux:

    npm install @reduxjs/toolkit react-redux
    
  3. Set up the backend:
    We'll use json-server, a lightweight tool to simulate a RESTful API. Install it globally with:

    npm install -g json-server
    

Project Structure

Here's how to structure your project:

/my-movie-app
 ├── data
 │   └── db.json
 ├── src
 │   ├── components
 │   │   ├── CardComponent
 │   │   ├── Modal
 │   │   └── Movies.tsx
 │   └── state
 │       └── movies
 │           └── store.ts

Inside db.json, paste movie data in the following format:

{
  "movies": [
    { "id": "1", "title": "Inception", "year": "2010", "description": "A thief who steals corporate secrets through the use of dream-sharing technology is given the inverse task of planting an idea into the mind of a CEO.", "thumbnail": "image_url" }
  ]
}

Start the JSON Server

To run your mock backend, start the JSON server with:

json-server --watch data/db.json --port 8080

Creating the API Slice

Within the movies folder, create moviesApiSlice.ts and add the following:

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

export const moviesApiSlice = createApi({
  reducerPath: 'movies',
  baseQuery: fetchBaseQuery({ baseUrl: 'http://localhost:8080' }),
  endpoints: (builder) => ({
    getMovies: builder.query({
      query: () => '/movies',
    }),
    addMovie: builder.mutation({
      query: (movie) => ({
        url: '/movies',
        method: 'POST',
        body: movie,
      }),
    }),
    updateMovie: builder.mutation({
      query: (movie) => ({
        url: `/movies/${movie.id}`,
        method: 'PUT',
        body: movie,
      }),
    }),
    deleteMovie: builder.mutation({
      query: ({ id }) => ({
        url: `/movies/${id}`,
        method: 'DELETE',
      }),
    }),
  }),
});

export const { useGetMoviesQuery, useAddMovieMutation, useDeleteMovieMutation, useUpdateMovieMutation } = moviesApiSlice;

Setting Up Redux Store

Set up your Redux store in store.ts as follows:

import { configureStore } from '@reduxjs/toolkit';
import { moviesApiSlice } from './movies/moviesApiSlice';

const store = configureStore({
  reducer: {
    [moviesApiSlice.reducerPath]: moviesApiSlice.reducer,
  },
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware().concat(moviesApiSlice.middleware),
});

export default store;

Integrate the Redux store in your main.tsx file by wrapping your <App /> component with the Provider:

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { Provider } from 'react-redux';
import store from './state/store';

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
);

Building the Movies Component

Next, we’ll create our Movies component to handle CRUD operations:

import React, { useState } from 'react';
import { useGetMoviesQuery, useAddMovieMutation } from '../state/movies/moviesApiSlice';

const Movies = () => {
  const { data: movies = [], error, isLoading } = useGetMoviesQuery();
  const [addMovie] = useAddMovieMutation();

  const handleAddMovie = async (newMovie) => {
    await addMovie(newMovie);
  };

  // Add a form to capture new movie information and render list of movies here...
  return (<div>{/* Movie list rendering */}</div>);
};

export default Movies;

Creating MovieCard and EditModal Components

Create a MovieCard.tsx to display individual movie details and an EditModal to facilitate editing movie data.

Managing Data Caching with RTK Query

RTK Query automatically caches results from API calls, which can lead to discrepancies in data if not managed correctly. To ensure accurate data representation after mutations, invalidate the cache using provided tags in your moviesApiSlice.

getMovies: builder.query({
    query: () => '/movies',
    providesTags: ['Movies'],
}),
addMovie: builder.mutation({
    query: (movie) => ({
        url: '/movies',
        method: 'POST',
        body: movie,
    }),
    invalidatesTags: ['Movies'],
}),

This way, you refresh the cache each time a mutation occurs, ensuring that users see the most up-to-date information.

Error Handling and Loading States

Implement user-friendly error and loading states for a better UI experience. Instead of displaying generic messages, provide detailed feedback or loading spinners to inform users about ongoing processes.

Best Practices

  • Modular API Slices: Organize multiple API slices to keep your codebase maintainable.
  • Redux DevTools: Use this tool for seamless debugging and monitoring state changes.
  • Prefetching Data: Consider using prefetching for a smoother user experience.

Conclusion

By following this guide, you've learned how to effectively integrate RTK Query with Redux Toolkit to build a responsive CRUD React application. You now possess the skills to handle data fetching, state management, and even caching strategies. Happy coding!

Advertisement