Redux Toolkit
Pinia like Vue
Redux Toolkit (also known as "RTK") is our officially recommended way of writing Redux logic. Redux Toolkit Chinese official website Redux Toolkit Video tutorials United States
Install
npm i @reduxjs/toolkit react-redux
index.js
transfer store
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import { Provider } from 'react-redux'
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<Provider store={store}>
<App />
</Provider>
);
Example Chapter 1 (Simple and direct)
Create a store
file in the root directory
Expose store/index.js
import { configureStore } from "@reduxjs/toolkit"
import counterReducer from "./modules/counterStore"
import channelReducer from "./modules/channelStore"
const store = configureStore({
reducer:{
counter: counterReducer,
channel: channelReducer,
}
})
export default store
store/modules/counterStore.js
Writing Logic
import { createSlice } from "@reduxjs/toolkit"
const counterStore = createSlice({
name: 'counter',
// Initial state data
initialState:{
count: 0
}
// Modify the synchronization method of data
reducers: {
increment (state) {
state.count++
},
decrement (state){
state.count--
},
addToNum (state, action){
state.count = action.payload
},
}
})
export const { increment, decrement, addToNum } = counterStore.actions
export default counterStore.reducer
Front-end
use
import { useEffect } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { decrement, increment, addToNum } from './store/modules/counterStore'
import { fetchChannelList } from './store/modules/channelStore'
function App () {
const { count } = useSelector(state => state.counter)
const { channelList } = useSelector(state => state.channel)
const dispatch = useDispatch()
useEffect(() => {
// Use useEffect to trigger asynchronous request execution
dispatch(fetchChannelList())
},[dispatch])
return (
<div className="App">
<button onClick={() => dispatch(decrement())}>-</button>
<span>{count}</span>
<button onClick={() => dispatch(addToNum(10))}>add to 10</button>
<button onClick={() => dispatch(addToNum(20))}>add to 20</button>
<ul>
{
channelList.map(item => <li key={item.id}>{item.name}</li>)
}
</ul>
</div>
)
}
store/modules/channelStore.js
Writing asynchronous data logic
import { createSlice } from "@reduxjs/toolkit";
import axios from "axios";
const channelStore = createSlice({
name: 'channel',
initialState: {
channelList: []
},
reducers: {
setChannels(state, action) {
state.channelList = action.payload;
}
}
});
const url = 'api-link';
export const fetchChannelList = () => {
return async (dispatch) => {
try {
const res = await axios.get(url);
dispatch(setChannels(res.data.data.channels));
} catch (error) {
// Handling Errors
console.error("Error fetching channel list:", error);
}
};
};
export const { setChannels } = channelStore.actions;
export default channelStore.reducer;
Example Chapter 2 (Logic)
Create store
file
Expose store/index.js
import { configureStore } from "@reduxjs/toolkit"
import counterReducer from "./modules/counterStore"
import channelReducer from "./modules/channelStore"
const store = configureStore({
reducer:{
counter: counterReducer,
channel: channelReducer,
}
})
export default store
store/modules/counterStore.js
Writing Logic
import { createSlice } from "@reduxjs/toolkit"
const counterStore = createSlice({
name: 'counter',
// Initial state data
initialState:{
count: 0
}
// Modify the synchronization method of data
reducers: {
increment (state) {
state.count++
},
decrement (state){
state.count--
},
addToNum (state, action){
state.count = action.payload
},
}
})
export const { increment, decrement, addToNum } = counterStore.actions
export const selectAllCounter = (state) => state.counter
export default counterStore.reducer
Front-end
use
import { useEffect } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { decrement, increment, addToNum, selectAllCounter } from './store/modules/counterStore'
import { fetchChannelList, selectAllChannel } from './store/modules/channelStore'
function App () {
const { count } = useSelector(selectAllCounter)
const { channelList } = useSelector(selectAllChannel)
const dispatch = useDispatch()
useEffect(() => {
// Use useEffect to trigger asynchronous request execution
dispatch(fetchChannelList())
},[dispatch])
return (
<div className="App">
<button onClick={() => dispatch(decrement())}>-</button>
<span>{count}</span>
<button onClick={() => dispatch(addToNum(10))}>add to 10</button>
<button onClick={() => dispatch(addToNum(20))}>add to 20</button>
<ul>
{
channelList.map(item => <li key={item.id}>{item.name}</li>)
}
</ul>
</div>
)
}
store/modules/channelStore.js
Writing asynchronous data logic
import { createSlice } from "@reduxjs/toolkit";
import axios from "axios";
const channelStore = createSlice({
name: 'channel',
initialState: {
channelList: []
},
reducers: {
setChannels(state, action) {
state.channelList = action.payload;
}
}
});
const url = 'api-link';
export const fetchChannelList = () => {
return async (dispatch) => {
try {
const res = await axios.get(url);
dispatch(setChannels(res.data.data.channels));
} catch (error) {
// Handling Errors
console.error("Error fetching channel list:", error);
}
};
};
export const { setChannels } = channelStore.actions;
export const selectAllChannel = (state) => state.channel
export default channelStore.reducer;
nanoid
nanoid
is a utility function for generating a unique identifier (UUID), which is usually used to generate a unique identifier for an action in Redux or to generate a unique key. In Redux Toolkit, the nanoid
function can be imported from the @reduxjs/toolkit
package.
import { nanoid } from "@reduxjs/toolkit";
nanoid()
Add data
Advanced writing methods reducer
and prepare
store/modules/postsStore.js
writes asynchronous data logic
import { createSlice, nanoid } from "@reduxjs/toolkit"
const postsStore = createSlice({
name:'posts',
initialState: {
list = []
},
reducers: {
postAdded: {
reducer(state, action){
state.list.push(action.payload)
},
prepare(title, content){
return {
payload: {
id: nanoid(),
title,
content
}
}
}
}
}
})
export const { postAdded } = counterStore.actions
export const selectAllPosts = (state) => state.posts
export default postsStore.reducer
Front-end
use
import { useState } from "react"
import { useDispatch } from 'react-redux'
import { postAdded } from "./postsStore"
function App () {
const dispatch = useDispatch()
const [title, setTitle] = useState('')
const [content, setContent] = useState('')
const onTitleChanged = e => setTitle(e.target.value)
const onContentChanged = e => setContent(e.target.value)
const onSavePostCLicked = () => {
if(title && content) {
dispatch(postAdded(title, content))
setTitle('')
setContent('')
}
}
return (
<div className="App">
<form>
<label htmlFor="postTitle">Post Title:</label>
<input type="text" id="postTitle" name="postTitle" value={title} onChange={onTitleChanged} />
<label htmlFor="postContent">Content:</label>
<textarea id="postContent" value={content} onChange={onContentChanged} />
<button type="button" onClick={onSavePostCLicked}>Save Post</button>
</form>
</div>
)
}
Axios (Api)
createAsyncThunk
store/modules/postsStore.js
Writing asynchronous data logic
import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
import axios from "axios";
const url = 'api-link';
// Create Asynchronous action
export const fetchPostList = createAsyncThunk('post/fetchPostList',async () => {
try {
const res = await axios.get(url);
return res.data.data.posts;
} catch (error) {
// Handling Errors
// console.error("Error fetching post list:", error);
// throw error;
return error.message
}
}
);
const postsStore = createSlice({
name: 'posts',
initialState: {
posts: [],
status: 'idle', // Adding asynchronous state management
error: null
},
reducers: {
postAdded(state, action) {
state.posts.push(action.payload);
}
},
extraReducers: (builder) => {
builder
.addCase(fetchPostList.pending, (state) => {
state.status = 'loading';
})
.addCase(fetchPostList.fulfilled, (state, action) => {
state.status = 'succeeded';
state.posts.push(action.payload);
})
.addCase(fetchPostList.rejected, (state, action) => {
state.status = 'failed';
state.error = action.error.message;
});
}
});
export const { postAdded } = postsStore.actions;
export const selectAllPosts = (state) => state.posts.posts;
export const getPostsStatus = (state) => state.posts.status;
export const getPostsError = (state) => state.posts.error;
export default postsStore.reducer;
Two ways of writing
Writing method 1
extraReducers: (builder) => {
builder
.addCase(fetchPostList.pending, (state) => {
state.status = 'loading';
})
.addCase(fetchPostList.fulfilled, (state, action) => {
state.status = 'succeeded';
state.posts.push(action.payload);
})
.addCase(fetchPostList.rejected, (state, action) => {
state.status = 'failed';
state.error = action.error.message;
});
}
Writing 2
extraReducers: {
[fetchPostList.pending]: (state) => {
state.status = 'loading';
},
[fetchPostList.fulfilled]: (state, action) => {
state.status = 'succeeded';
state.posts.push(action.payload);
},
[fetchPostList.rejected]: (state, action) => {
state.status = 'failed';
state.error = action.error.message;
}
}
Front end
import { selectAllPosts, getPostsStatus, getPostsError, fetchPostList } from './postsStore'
import { useDispatch, useSelector } from 'react-redux'
import { useEffect } from "react"
import PostsExcerpt from "./PostsExcerpt"
function App(){
const dispatch = useDispatch()
const posts = useSelector(selectAllPosts);
const postStatus = useSelector(getPostsStatus);
const error = useSelector(getPostsError);
useEffect(() => {
if(postStatus === 'idle'){
dispatch(fetchPostList())
}
},[postsStatus,dispatch])
let content;
if(postStatus === 'loading'){
content = <p>"Loading..."</p>
}else if(postStatus === 'succeeded'){
const orderedPosts = posts.slice().sort((a,b) => b.date.localeCompare(a.date))
content = orderedPosts.map(post => <PostsExcerpt key={post.id} post={post} />)
}else if(postStatus === 'failed'){
content = <p>{error}</p>
}
return (
<div>
{content}
</div>
)
}
Post Axios
// Create an asynchronous action
export const fetchPostList = createAsyncThunk('post/fetchPostList',async (data) => {
try {
const res = await axios.post(url, data);
return res.data;
} catch (error) {
// Handling Errors
// console.error("Error fetching post list:", error);
// throw error;
return error.message
}
}
);
.addCase(fetchPostList.fulfilled, (state, action) => {
state.status = 'succeeded';
state.posts.push(action.payload.name);
})
Template
Usually write initialState
in a function
import { createSlice } from "@reduxjs/toolkit";
const initialState = {
name: "Hoo"
};
const counterStore = createSlice({
name: 'counter',
initialState,
reducers: {
setName(state, action) {
state.name = action.payload;
}
}
});
export const { setName } = counterStore.actions;
export default counterStore.reducer;
Other ways to write reference
// front end
dispatch(setName({
name,
age,
email
}))
// back end
setName(state, action) {
const { name, age, email } action.payload;
}