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:
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.
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:
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:
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:
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:
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:
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.
Add Comment