...
Why?
Instead of wasting time on boilerplate, you can get started immediately. The standards followed are the recommended way to write Redux logic for modern JS applications. I found that I am writing less code for core logic & can see this being utilized in any application that needs state management.
Tools:
https://redux.js.org/ (core library, state management)
https://immerjs.github.io/immer/ (allows you to mutate the state)
https://github.com/reduxjs/redux-thunk (handles async actions)
https://github.com/reduxjs/reselect (simplifies reducer functions)
Set-up:
This example will implement the configureStore()
, useSelector()
, & useDispatch()
tools to run a simple CRUD application. We'll be running off create-react-app for this example.
If you haven’t a react app running yet, run:
npx create-react-app app-name
1.) In the root folder, install react-redux & redux-toolkit:
npm install react-redux @reduxjs/toolkit
2.) Configure the store in a separate store.js
file:
Code Block |
---|
import { configureStore } from “@reduxjs/toolkit”;
export const store = configureStore({
reducer: {},
}); |
The configureStore()
wraps the createStore
from Redux & provides simple configuration/defaults. We'll add our reducer later.
3.) From here we want to import our store & pass it as a store in our Provider.
Code Block |
---|
import { store } from './store';
import { Provider } from 'react-redux';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>
); |
4.) Create a features folder in the root & create a Posts.js file:
Code Block |
---|
import { createSlice } from "@reduxjs/toolkit";
const initialState = {
value: [
{id: '1', title: 'title 1', content: 'test'},
{id: '2', title: 'title 2', content: 'test'},
]
}
export const Posts = createSlice({
name: 'posts',
initialState,
reducers: {
addPost: (state, action) => {
state.value.push(action.payload);
},
deletePost: (state, action) => {
state.value = state.value((item) => item.id !== action.payload.id)
}
}
})
export const { addPost, deletePost } = Posts.actions;
export default Posts.reducer;
|
This is the where the Action logic will go:
Code Block |
---|
addPost: (state, action) => {
state.value.push(action.payload);
} |
state
arg is the draft state provided from Immer.
action
arg is the object being dispatched.
Importing Actions:
export const { addPost, deletePost } = Posts.actions;
Importing Reducers:
export default Posts.reducer;
5.) Import & add the Post reducer to our store:
Code Block |
---|
import { configureStore, createStore } from '@reduxjs/toolkit';
import postReducer from "../features/post/posts.js";
export const store = configureStore({
reducer: {
posts: postReducer
},
});
|
6.) Create a Posts.js in our component folder & import useSelector
and useDispatch
from the react-redux library:
Code Block |
---|
import { useSelector, useDispatch } from "react-redux";
import { addPost, deletePost } from "../posts";
const PostList = () => {
const dispatch = useDispatch();
const posts = useSelector(state => state.posts.value);
const renderedPosts = posts.map((p, i) => {
return (
<article
key={p.id}>
<h3>{p.title}{p.id}</h3>
<p>{p.content.substring(0, 100)}</p>
<button onClick={()=> {
dispatch(deletePost({
id: p.id
}))
}}>X</button>
</article>
)
})
return (
<section>
<h2>Posts</h2>
{renderedPosts}
<button onClick={() => {
dispatch(addPost({
id: posts[posts.length - 1].id + 1,
title: 'Testing',
content: 'woo haa'
}))
}}>Submit</button>
</section>
)
}
export default PostList; |
We can access the state through:
const posts = useSelector(state => state.posts.value);
We can dispatch through:
Code Block |
---|
dispatch(addPost({
id: posts[posts.length - 1].id + 1,
title: 'Testing',
content: 'woo haa'
})) |
CreateAsyncThunk:
Summary
With Redux-toolkit we’re able to rid ourselves from having complex store configurations & unnecessary files following boilerplate. I’m planning to add more to this. Highly recommend reading through the Redux-toolkit docs for full comprehension.