Idea Native Redux requires too much code and external packages Redux team provides opinionated way to work with redux - Redux Toolkit Install npm install @reduxjs/toolkit react-redux Redux basics In this post I will implement similar app as I did in in basic Redux. In redux we deal with STORE , ACTION , REDUCER , DISPATCH STORE keeps all states ACTION is an object which describes what we want to do with a state DISPATCH sends an ACTION to a REDUCER REDUCER sets the initial state and updates the state in STORE in accordance to an ACTION we provide Redux Toolkit vs Redux Split your app into feature files Keep REDUCER & ACTION for a single feature in a single file The convention is to have SLICE as a suffix in the file name Entire application state is split into slices In our example we have 3 features with corresponding files: counterSlice.js, loginSlice.js & usersSlice.js Initial state is stored in the initialState object Reducers are stored in the reducers object In REDUCER with Toolkit we can mutate the state and do not need to explicitly return the state (Immer package is inside) ACTION CREATORS will be created automatically based on reducer name functions No need to use combineReducers() In Toolkit a reducer responses only to action types generated in the same slice extraReducers allows a slice to respond to action types from another slice Toolkit has built-in thunk implementation Dev tools package is built-in in, just need to install the browser extension . Slice (reducer + action) Counter slice Counter slice adds and subtracts number. Here we have extraReducer written in not preferable way, which can respond to the action from the greetings slice.
// redux-toolkit-demo/slices/counterSlice.js
import { createSlice } from '@reduxjs/toolkit'
const initialState = {
counter: 0
}
const counterSlice = createSlice({
name: 'counterSlice',
initialState,
reducers: {
increment: (state, action) => {
state.counter += (action.payload || 1)
},
decrement: (state, action) => {
state.counter -= (action.payload.num || 1)
}
},
// not preferable way of extraReducers syntax
extraReducers: {
'greetingsSlice/changeGreeting': (state, action) => {
console.log('I can respond to changeGreeting() action of greetingsSlice from counterSlice')
}
}
})
export default counterSlice.reducer
export const { increment, decrement } = counterSlice.actions
Login slice Login slice just toggles the login flag. Here we have extraReducer written in preferable way, which can respond to the action from the greetings slice.
// redux-toolkit-demo/slices/loginSlice.js
import { createSlice } from '@reduxjs/toolkit'
import { changeGreeting } from './greetingsSlice'
const initialState = {
isLogged: false
}
const loginSlice = createSlice({
name: 'loginSlice',
initialState,
reducers: {
login: (state, action) => {
state.isLogged = !state.isLogged
}
},
// preferable way of extraReducers syntax
extraReducers: (builder) => {
builder.addCase(changeGreeting, (state, action) => {
console.log('I can respond to changeGreeting() action of greetingsSlice from loginSlice')
})
}
})
export default loginSlice.reducer
export const { login } = loginSlice.actions
Users slice This is async thunk middleware, which fetches data from the api.
// redux-toolkit-demo/slices/usersSlice.js
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'
import axios from 'axios'
import sleeper from '/functions/sleeper'
const initialState = {
loading: false,
users: [],
err: ''
}
// generates 'pending', 'fulfilled' & 'rejected' action types automatically
const fetchUsers = createAsyncThunk('users/fetchUsers', () => {
return axios.get('https://jsonplaceholder.typicode.com/users')
.then(sleeper(1000))
.then(res => res.data)
})
const usersSlice = createSlice({
name: 'usersSlice',
initialState,
extraReducers: (builder) => {
builder.addCase(fetchUsers.pending, (state, action) => {
state.loading = true
})
builder.addCase(fetchUsers.fulfilled, (state, action) => {
state.loading = false
state.users = action.payload
state.error = ''
})
builder.addCase(fetchUsers.rejected, (state, action) => {
state.loading = false
state.users = []
state.error = action.error.message
})
}
})
export default usersSlice.reducer
export { fetchUsers }
Store
// redux-toolkit-demo/store.js
import { configureStore } from '@reduxjs/toolkit'
import { createLogger } from 'redux-logger'
import counter from '/redux-toolkit-demo/slices/counterSlice.js'
import login from '/redux-toolkit-demo/slices/loginSlice.js'
import greetings from './slices/greetingsSlice.js'
import users from './slices/usersSlice.js'
// #region LOGGER MIDDLEWARE
const logger = createLogger({})
// #endregion
export const store = configureStore({
reducer: {
counter,
login,
greetings,
users
},
middleware: (defaultMiddleware) => defaultMiddleware().concat(logger),
devTools: true
})
Component
import { Code, H, Hs, Lnk, jsxToStr, LazyImg } from '/components/post/reExport'
import { Provider, useSelector, useDispatch } from 'react-redux'
import { store } from '/redux-toolkit-demo/store'
import { increment, decrement } from '/redux-toolkit-demo/slices/counterSlice.js'
import { login } from '/redux-toolkit-demo/slices/loginSlice.js'
import { changeGreeting } from '../redux-toolkit-demo/slices/greetingsSlice.js'
import { fetchUsers } from '/redux-toolkit-demo/slices/usersSlice.js'
// #region COMPONENT with useSelector() & useDispatch()
const style = { border: '2px solid grey', padding: '10px', margin: '10px', maxWidth: '500px' }
function Component() {
const counter = useSelector(state => state.counter.counter)
const isLogged = useSelector(state => state.login.isLogged)
const users = useSelector(state => state.users)
const dispatch = useDispatch()
return (
<div style={style}>
<div>Counter: <strong>{counter}</strong></div>
<button onClick={() => dispatch(increment())}>Increment +1</button> 
<button onClick={() => dispatch(decrement({ num: 3 }))}>Decrement -3</button>
<div>isLogged: <strong>{isLogged.toString()}</strong></div>
<button onClick={() => dispatch(login())}>Sign in/out</button><br />
<button onClick={() => dispatch(fetchUsers())}>Fetch users</button><br />
<div>
{users.loading && 'Loading...'}
{users.err && users.err}
{!users.loading && !!users.users.length && users.users.map(user => <div key={user.id}>{user.name}</div>)}
</div>
</div>
)
}
// #endregion
<Provider store={store}>
<Component />
</Provider>
TypeScript Watch this youtube video from Vishwas about typescript in redux.