Guide to Using Redux Toolkit Query Mutation
Today, we are going to learn how to add data using Redux Toolkit. In Redux Toolkit Query terms, changing data is called a mutation, and reading data is called a query. We will understand how to implement this and the logic behind it. We will also explore the tag system that Redux Toolkit Query uses. So, without further ado, let's get started.
Project Setup
We will have a single button for adding data to keep things simple. We will display the data we fetch, and when "Add Data" is clicked, we will run a mutation to add data to the display. The data will come from a library called Faker, and the backend will be a JSON server. However, you can apply these concepts using your own API because the principles remain the same.
npm create vite@latest
npm i
Create a project using Vite or your preferred tool to set up a React project. After setting up and installing the dependencies, you will need these packages for your Redux Toolkit Query setup.
npm install @reduxjs/toolkit
npm install react-redux
npm install --save-dev @faker-js/faker
npm i json-server
Setting up JSON Server
Inside your package.json add a script called start:server
with value npx json-server db.json
. Inside your root project create a db.json
.
// package.json
...
"scripts": {
"start:server": "npx json-server db.json", // Add this...
"dev": "vite",
"build": "vite build",
"lint": "eslint .",
"preview": "vite preview"
}
...
// db.json
{
"users": [
{
"name": "Kumar",
"id": "1"
}
]
}
Test if your server is set up correctly by running npm run start:server
.
Creating Redux Store and usersApi for RTK Query
Create a new folder inside your src
directory called store
. Inside this, create a folder named apis
, and within apis
, create a file called usersApi.js
. All the queries or mutations will be consolidated here.
// src/store/apis/usersApi.js
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
const usersApi = createApi({
reducerPath: "users",
baseQuery: fetchBaseQuery({
baseUrl: "http://localhost:3000",
}),
endpoints(builder) {
return {
fetchUsers: builder.query({
query: () => {
return {
url: "/users",
method: "GET",
};
},
}),
};
},
});
export const { useFetchUsersQuery } = usersApi;
export { usersApi };
I discussed this setup in detail in my previous blog post, which you can view here:
https://shiwanshudev.hashnode.dev/how-to-set-up-redux-toolkit-query
In summary, we imported createApi
from Redux Toolkit. createApi
takes an object with three main properties: reducerPath
, baseQuery
, and endpoints
.
The reducerPath
is the name used inside the store. The baseQuery
takes an object with a baseUrl
property, which is your API's base URL. Lastly, endpoints
is where we define all the mutations and queries for our usersApi
.
The endpoint is a function that takes builder
as an argument. We need to return an object with keys for various operations. The fetchUsers
property is defined for fetching users. Since we are reading data, we use query
here. For a mutation, as we will see later, we use builder.mutation
. We return an object from builder.query
with the query
property, which is the URL configuration for making the request. The url
property will be appended to the baseUrl
we defined earlier. It might seem confusing at first, but you will get used to the pattern.
Be very careful when importing createApi
and baseQuery
. Often, the "react" at the end gets left out in the import statement if you use your IDE's autocomplete feature.
Setting up our Redux store
// src/store/index.js
import { configureStore } from "@reduxjs/toolkit";
import { usersApi } from "./apis/usersApi";
import { setupListeners } from "@reduxjs/toolkit/query";
const store = configureStore({
reducer: {
[usersApi.reducerPath]: usersApi.reducer,
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(usersApi.middleware),
});
setupListeners(store.dispatch);
export { store };
// src/main.tsx
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import App from "./App.jsx";
import "./index.css";
import { Provider } from "react-redux";
import { store } from "./store/index.js";
createRoot(document.getElementById("root")).render(
<StrictMode>
<Provider store={store}>
<App />
</Provider>
</StrictMode>
);
Inside the store file located at src/store/index.js
, we configure our reducers by using the [usersApi.reducerPath]
property as usersApi.reducer
. This means we evaluate usersApi.reducerPath
and use it as a key for usersApi.reducer
. The []
syntax does not create an array. In configureStore
, we also add our middleware and finally export the store.
Inside src/main.tsx
, we set up the Provider and pass it the store we exported earlier.
Using the Redux Toolkit Query to fetch data
// src/App.jsx
import "./App.css";
import { useFetchUsersQuery } from "./store/apis/usersApi";
function App() {
const { data, isLoading, error } = useFetchUsersQuery();
console.log(data);
const handleAddUser = () => {};
return (
<div className="container">
<button className="btn" onClick={handleAddUser}>
Add Users
</button>
<div className="users">
{data &&
data.map((user) => {
return <div className="user">{user.name}</div>;
})}
</div>
</div>
);
}
export default App;
Inside the App.jsx
file located in the src
folder, we call useFetchUsersQuery()
, which we can destructure into data
, isLoading
, and error
. We use this data to display our users. Ensure the JSON server is running with npm run start:server
, and then start your app with npm run dev
.
Adding a User with Mutation
// src/store/apis/usersApi.js
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
const usersApi = createApi({
reducerPath: "users",
baseQuery: fetchBaseQuery({
baseUrl: "http://localhost:3000",
}),
endpoints(builder) {
return {
fetchUsers: builder.query({
query: () => {
return {
url: "/users",
method: "GET",
};
},
providesTags: ["User"], // ADD THIS
}),
addUser: builder.mutation({
query: (user) => {
return {
url: "/users",
method: "POST",
body: {
name: user.name,
id: user.id,
},
};
},
invalidatesTags: ["User"], // ADD THIS
}),
};
},
});
export const { useFetchUsersQuery, useAddUserMutation } = usersApi;
export { usersApi };
Similar to how we defined fetchUsers
inside the endpoints return object, we define another property called addUser
. This will use builder.mutation
instead of builder.query
because we are modifying data. Note that the object it takes has a query property again; it is more like a set of configurations for the URL, method, etc., so don't confuse it with the builder.mutation
property. Since we are creating a user, we set the method to POST and the URL to "/users" because that is what we defined in our db.json
earlier. The body will be the data we pass into this query from our App.jsx.
ProvidesTags and InvalidatesTags Property:
The tags are essentially used to enable automatic data refetching. What does this mean? If you remove both providesTags and invalidatesTags, you will notice that even though the data appears in console.log when printed in App.jsx, the view will not update. To prevent this, we associate a tag with each request. If the URL changes, we need to use a function call that also changes according to the URL for this to work correctly. For more information, refer to the official documentation below.
Official Documentation:
https://redux-toolkit.js.org/rtk-query/usage/automated-refetching
Using the mutation inside our App
// src/App.jsx
import "./App.css";
import { useAddUserMutation, useFetchUsersQuery } from "./store/apis/usersApi";
import { faker } from "@faker-js/faker";
function App() {
const { data, isLoading, error } = useFetchUsersQuery();
const [addUser, results] = useAddUserMutation();
console.log(data);
const handleAddUser = () => {
addUser({
name: faker.internet.userName(),
id: faker.string.uuid(),
});
};
return (
<div className="container">
<button className="btn" onClick={handleAddUser}>
Add Users
</button>
<div className="users">
{data &&
data.map((user) => {
return (
<div key={user.id} className="user">
{user.name}
</div>
);
})}
</div>
</div>
);
}
export default App;
Inside our App.jsx, we call the useAddUserMutation function exported from usersApi.js. The line const [addUser, results] = useAddUserMutation();
sets up our addUser mutation. Unlike the query above it, this line returns an array. We destructure it to use addUser with the object we want to send as the body of the POST request.
...
const handleAddUser = () => {
addUser({
name: faker.internet.userName(),
id: faker.string.uuid(),
});
};
...
Here, as you can see, we are using faker to generate a random username and ID, which we then pass to addUser
. This initiates the POST request. We attach the handleAddUser
function to the button we created to trigger this action. Finally, we run the app with the server running to make this request.
Thank you for reading this. If you have any questions, feel free to reach out to me.
Subscribe to my newsletter
Read articles from Shiwanshu Shubham directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Shiwanshu Shubham
Shiwanshu Shubham
Hey there, I'm Shiwanshu, a passionate frontend developer and designer hailing from India. With proficiency in NextJS, ReactJS, CSS3, HTML5, Figma, UI Design, and Typescript, I've embarked on a journey into the tech space. This blog is a documentation of my progress as I delve deeper into the world of technology.