In modern web development, managing state efficiently is crucial for creating fast, scalable applications. Redux Toolkit (RTK) has emerged as the standard way to manage state in React applications, providing a set of tools and best practices to simplify Redux development.
In this advanced guide, we will explore how to implement CRUD (Create, Read, Update, Delete) operations using RTK in ReactJS. We will leverage RTK’s createSlice, createAsyncThunk, and configureStore to handle asynchronous actions and server-side data fetching.
Prerequisites
Before diving into the implementation, you should have a basic understanding of:
- ReactJS fundamentals
- Redux (or at least state management in React)
- How to use React hooks (
useState,useEffect,useDispatch,useSelector)
If you’re not familiar with Redux Toolkit, check out the official documentation here, as we will build upon its concepts.
Steps to Implement CRUD Operations with RTK
Step 1: Set Up Your Project
First, let’s create a new React app and install necessary dependencies:
npx create-react-app rtk-crud-app
cd rtk-crud-app
npm install @reduxjs/toolkit react-redux axios
@reduxjs/toolkit: Toolkit for efficient Redux development.react-redux: React bindings for Redux.axios: Promise-based HTTP client for making API requests.
Step 2: Set Up Redux Store
We will configure a Redux store to hold our application’s state. Create a new directory src/redux and add a file called store.js to configure the Redux store:
src/redux/store.js
import { configureStore } from '@reduxjs/toolkit';
import { postsReducer } from './postsSlice';
const store = configureStore({
reducer: {
posts: postsReducer,
},
});
export default store;
Step 3: Create a Slice for Post Data
Now, let’s create a slice to manage the state for our CRUD operations. We will use createSlice to handle actions like adding, updating, deleting, and fetching posts.
Create a file postsSlice.js inside the src/redux folder:
src/redux/postsSlice.js
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
import axios from 'axios';
// Fetch posts from the API
export const fetchPosts = createAsyncThunk('posts/fetchPosts', async () => {
const response = await axios.get('https://jsonplaceholder.typicode.com/posts');
return response.data;
});
// Create a new post
export const createPost = createAsyncThunk('posts/createPost', async (newPost) => {
const response = await axios.post('https://jsonplaceholder.typicode.com/posts', newPost);
return response.data;
});
// Update a post
export const updatePost = createAsyncThunk('posts/updatePost', async (updatedPost) => {
const response = await axios.put(`https://jsonplaceholder.typicode.com/posts/${updatedPost.id}`, updatedPost);
return response.data;
});
// Delete a post
export const deletePost = createAsyncThunk('posts/deletePost', async (postId) => {
await axios.delete(`https://jsonplaceholder.typicode.com/posts/${postId}`);
return postId;
});
const postsSlice = createSlice({
name: 'posts',
initialState: {
posts: [],
status: 'idle', // idle, loading, succeeded, failed
error: null,
},
reducers: {},
extraReducers: (builder) => {
builder
.addCase(fetchPosts.pending, (state) => {
state.status = 'loading';
})
.addCase(fetchPosts.fulfilled, (state, action) => {
state.status = 'succeeded';
state.posts = action.payload;
})
.addCase(fetchPosts.rejected, (state, action) => {
state.status = 'failed';
state.error = action.error.message;
})
.addCase(createPost.fulfilled, (state, action) => {
state.posts.push(action.payload);
})
.addCase(updatePost.fulfilled, (state, action) => {
const index = state.posts.findIndex((post) => post.id === action.payload.id);
if (index !== -1) {
state.posts[index] = action.payload;
}
})
.addCase(deletePost.fulfilled, (state, action) => {
state.posts = state.posts.filter((post) => post.id !== action.payload);
});
},
});
export const postsReducer = postsSlice.reducer;
Step 4: Integrate Redux Store with React
Now that our store is set up, we need to provide it to our React app using the Provider from react-redux. Modify the src/index.js file:
src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import { Provider } from 'react-redux';
import store from './redux/store';
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
Step 5: Create Components for CRUD Operations
Now we will create a set of components for displaying, creating, updating, and deleting posts. We will use React hooks such as useDispatch and useSelector to interact with Redux state.
src/components/PostsList.js
This component will fetch and display all posts.
import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { fetchPosts, deletePost } from '../redux/postsSlice';
const PostsList = () => {
const dispatch = useDispatch();
const { posts, status, error } = useSelector((state) => state.posts);
useEffect(() => {
if (status === 'idle') {
dispatch(fetchPosts());
}
}, [dispatch, status]);
const handleDelete = (id) => {
dispatch(deletePost(id));
};
let content;
if (status === 'loading') {
content = <div>Loading...</div>;
} else if (status === 'succeeded') {
content = (
<ul>
{posts.map((post) => (
<li key={post.id}>
{post.title}
<button onClick={() => handleDelete(post.id)}>Delete</button>
</li>
))}
</ul>
);
} else if (status === 'failed') {
content = <div>{error}</div>;
}
return (
<div>
<h2>Posts</h2>
{content}
</div>
);
};
export default PostsList;
src/components/CreatePost.js
This component will allow users to create a new post.
import React, { useState } from 'react';
import { useDispatch } from 'react-redux';
import { createPost } from '../redux/postsSlice';
const CreatePost = () => {
const [title, setTitle] = useState('');
const [body, setBody] = useState('');
const dispatch = useDispatch();
const handleSubmit = (e) => {
e.preventDefault();
if (title && body) {
dispatch(createPost({ title, body }));
setTitle('');
setBody('');
}
};
return (
<div>
<h2>Create a Post</h2>
<form onSubmit={handleSubmit}>
<div>
<label>Title</label>
<input
type="text"
value={title}
onChange={(e) => setTitle(e.target.value)}
required
/>
</div>
<div>
<label>Body</label>
<textarea
value={body}
onChange={(e) => setBody(e.target.value)}
required
/>
</div>
<button type="submit">Create Post</button>
</form>
</div>
);
};
export default CreatePost;
Step 6: Update Post Component
We can create a similar component for updating posts by selecting a post and editing its content.
src/components/UpdatePost.js
import React, { useState, useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { updatePost } from '../redux/postsSlice';
const UpdatePost = ({ postId }) => {
const dispatch = useDispatch();
const post = useSelector((state) =>
state.posts.posts.find((p) => p.id === postId)
);
const [title, setTitle] = useState('');
const [body, setBody] = useState('');
useEffect(() => {
if (post) {
setTitle(post.title);
setBody(post.body);
}
}, [post]);
const handleSubmit = (e) => {
e.preventDefault();
if (title && body) {
dispatch(updatePost({ id: postId, title, body }));
}
};
if (!post) {
return <div>Post not found</div>;
}
return (
<div>
<h2>Update Post</h2>
<form onSubmit={handleSubmit}>
<div>
<label>Title</label>
<input
type="text"
value={title}
onChange={(e) => setTitle(e.target.value)}
required
/>
</div>
<div>
<label>Body</label>
<textarea
value={body}
onChange={(e) => setBody(e.target.value)}
required
/>
</div>
<button type="submit">Update Post</button>
</form>
</div>
);
};
export default UpdatePost;
Step 7: Testing Everything
Now you can use the components (PostsList, CreatePost, UpdatePost) in your App.js to test CRUD operations.
src/App.js
import React, { useState } from 'react';
import PostsList from './components/PostsList';
import CreatePost from './components/CreatePost';
import UpdatePost from './components/UpdatePost';
const App = () => {
const [postId, setPostId] = useState(null);
return (
<div>
<h1>Redux Toolkit CRUD with React</h1>
<CreatePost />
{postId && <UpdatePost postId={postId} />}
<PostsList />
</div>
);
};
export default App;
Conclusion
With this setup, you now have a functional CRUD application using Redux Toolkit (RTK) in ReactJS. This example demonstrates best practices, including handling asynchronous actions with createAsyncThunk, state updates using createSlice, and integrating Redux with React using hooks like useDispatch and useSelector.
This pattern is scalable and easy to maintain as your application grows. You can extend this by adding additional features like pagination, authentication, or more complex data fetching strategies.
For more such blogs and updates follow Front-end Competency.
Follow NashTech Blogs for more amazing blogs.