Mastering Data Fetching: Integrating RTK Query with Redux Toolkit in Your React Movie App
- Published on
- Authors
- Name
- Binh Bui
- @bvbinh
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
- Understanding RTK Query and Core Concepts
- Integrating RTK Query with Redux Toolkit
- Handling Data Caching with RTK Query
- Error Handling and Loading States
- Best Practices
- Conclusion
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
Create a new React project:
Run the command:npm create vite@latest
Follow the prompts to set up your project.
Install dependencies:
Install@reduxjs/toolkit
andreact-redux
:npm install @reduxjs/toolkit react-redux
Set up the backend:
We'll usejson-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!