Firstly, what is side effects, side effects refer to any external interaction outside an existing client application such as fetching data from an API.
In Redux, middleware has always been used to perform asynchronous tasks. Asynchronous tasks means things you have to wait for, such as fetching data from an API. A middleware is designed to enable developers to write logic that has side effects. An example is a package called redux-thunk
.
Redux-thunk is used for asychronous logic (tasks).
Redux toolkit comes with built-in dependencies such as redux-thunk
, because Redux toolkit includes redux-thunk
by default, we can use createAsyncThunk
to make asynchronous requests.
createAsyncThunk
CreateAsyncThunk is where we perform asychronous tasks in our slice. It receives two parameters
- name of the action, the standard convention is "[slice name]/[action name]" such as
"posts/fetchPosts"
- The callback function that performs the API call and returns the result when it is finished. Our API call returns a promise (which is an object that represents the status of an asynchronous operation, in our case an API call).
For each action that is created using createAsyncThunk
, there are three probable state for the promise returned. pending
, fulfilled
, rejected
.
You decide what Redux should do in the three (3) different stages of the API call. Inside our slice we will add a property called extraReducers
that holds a couple functions to handle the return of the API: pending
, fulfilled
and rejected
.
extraReducers
You use extraReducers to handle actions that are created by createAsyncThunk
. Based on the status of the promise, we will update our state.
I will assume you know a little-bit about redux toolkit, I will speed run through the set-up
Attention
please note, all files for a single feature should be in the same folder. meaning everything concerning posts should be in a folder called posts
set up a store
// src/app/store.js
import { configureStore } from '@reduxjs/toolkit'
import postsReducer from '../features/posts/postsSlice'
export const store = configureStore({
reducer: {
// reducer for slice goes here
},
})
export default store
provide the store to the App
wrap the enitre app with the store.
// index.js
import App from './App';
import { store } from './app/store'
import { Provider } from 'react-redux'
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>
);
create a slice
// src/features/posts/postsSlice
import { createSlice } from '@reduxjs/toolkit'
import { createAsyncThunk } from '@reduxjs/toolkit'
import axios from "axios"
const BASE_URL = "https://jsonplaceholder.typicode.com/posts"
const initialState = {
posts: [],
status: "idle",
error: ""
}
export const fetchPosts = createAsyncThunk("posts/fetchPosts", async () => {
const response = await axios.get(BASE_URL)
console.log(response.data)
return response?.data
})
export const deletePost = createAsyncThunk("post/deletePost", async (initialPost) => {
const {id} = initialPost
try {
const response = await axios.delete(`${BASE_URL}/${id}`);
if (response?.status === 200) return initialPost;
return `${response.status} : ${response.statusText}`;
} catch (error) {
return error.message
}
})
const postsSlice = createSlice({
name: 'posts',
initialState,
reducers: {
// ==> normal reducer functions go here
},
extraReducers(builder) {
builder
.addCase(fetchPosts.pending, (state, action) => {
state.status = "loading"
})
.addCase(fetchPosts.fulfilled, (state, action) => {
state.status = "succeeded"
state.posts = state.posts.concat(action.payload);
})
.addCase(fetchPosts.rejected, (state, action) => {
state.status = "failed"
state.error = action.error.message
})
.addCase(deletePost.fulfilled, (state, action) => {
if (!action?.payload.id) {
console.log("could not delete");
console.log(action.payload)
return
}
const { id } = action.payload;
const OldPosts = state.posts.filter(post =>
post.id !== id)
state.posts = OldPosts
})
}
})
export default postsSlice.reducer;
create a bunch of selectors to access your state
The selectors make it easier so that if the nature of your state changes, you can update all of them in one place.
Attention
This is done while still inside the posts Slice.
// src/posts/postsSlice
export const selectAllPosts = (state) => state.posts.posts
export const getPostsError = (state) => state.posts.error
export const getPostsStatus = (state) => state.posts.status
add slice reducers to the store
// src/app/store.js
import { configureStore } from '@reduxjs/toolkit'
import postsReducer from '../features/posts/postsSlice'
export const store = configureStore({
reducer: {
posts: postsReducer
},
})
export default store
fetch this array of posts as soon as our app loads
// index.js
import { fetchPosts } from './features/posts/postsSlice';
store.dispatch(fetchPosts());
Posts component
// src/features/posts/Posts.jsx
import React from 'react'
import { useSelector } from 'react-redux/es/hooks/useSelector'
import { selectAllPosts, getPostsError, getPostsStatus } from './postsSlice'
import TableData from './TableData'
const Posts = () => {
// selectors to access state
const posts = useSelector(selectAllPosts);
const status = useSelector(getPostsStatus);
const error = useSelector(getPostsError);
let content;
if (status === "loading") {
content = <div className="text-center my-5">Loading...</div>
} else if (status === "succeeded") {
// change the order of the posts
const orderedPosts = posts.slice().sort((a, b) => a - b)
content = orderedPosts.map((post, i) => (
<TableData key={i} post={post} />
))
} else if (status === "failed") {
content = (
<>
<h1>Posts not found</h1>
<p className='text-center text-danger'>{error}</p>
</>
)
}
return (
<section className="section">
<div className="container">
<div className="row">
<div className="col-12 text-center">
<h3>Here are all the posts</h3>
</div>
</div>
<div className="row">
<div className="col-12">
{content}
</div>
</div>
</div>
</section>
)
}
export default Posts
TableData component
I used seperation of concerns to make re-usable components.
// src/features/posts/TableData.jsx
import React from 'react'
import { deletePost } from './postsSlice'
import { useDispatch } from 'react-redux'
import { useNavigate } from "react-router-dom";
const TableData = ({ post }) => {
const navigate = useNavigate();
const { id } = post;
const dispatch = useDispatch();
const handleDelete = () => {
try {
// dispatch action to store
dispatch(deletePost({ id })).unwrap();
navigate("/")
} catch (error) {
console.log(`Failed to delete the post ${error}`)
}
}
return (
<div className="item">
<div>
<h3>{post.title}</h3>
<p className="postCredit">
{post.body}
</p>
</div>
<div>
<button className="btn btn-danger" onClick={handleDelete}>
delete
</button>
</div>
</div>
)
}
export default TableData
App component
import './App.css';
import { BrowserRouter as Router, Route, Routes } from "react-router-dom"
import Posts from './features/posts/Posts';
function App() {
return (
<Router>
<Routes>
<Route path="/" element={<Posts />} />
</Routes>
</Router>
);
}
export default App;
CSS
Here is my CSS, you can put it in App.css
or index.css
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
font-size: 1rem;
}
body{
min-height: 100vh;
}
.App{
display: flex;
flex-direction: column;
justify-content: flex-start;
min-height: 100vh;
}
.section{
background-color: whitesmoke;
border: 1px solid blue;
flex: 1;
}
/* custom styling */
.item {
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
padding: 18px;
background-color: aqua;
border: 2px solid dodgerblue;
margin: 10px 0;
}
.item-text{
font-size: 1.2rem;
margin: 0;
padding: 0;
}
Add the following packages to package.json
and run npm install
"overrides": {
"autoprefixer": "10.4.5"
},
"@reduxjs/toolkit": "^1.8.3",
"bootstrap": "^5.1.3",
"react-router-dom": "^6.3.0",
"react-redux": "^8.0.2",
Thank you, Please follow me
Top comments (6)
helpfull
Thank you, please follow me.
can you provide githun repo
Top-notch
Helpful and Thanks
Thank you, please follow me.