Understand Redux Toolkit Basics: A Counter Example
A better way to use Redux
React is a great framework, library, whatever you choose to call it. However, there's one area it suffers in terms of performance: state management. The more complex an app becomes, the greater the need for React to rely on an external library such as Redux. Although there is Context API, a built-in hook for state management, it is only a lightweight solution suitable for passing data from a parent element to nested children components.
What Exactly is State in React?
A state is an object where the property values belonging to a component are stored. It is encapsulated data where properties are persistent between component renderings. When a state is changed or mutated, the UI or component re-renders.
Why Do We Need to Manage State?
Good question!
Every component in React may contain a state, and these states are managed internally by the component. For lightweight and smaller apps, sharing state data usually involves parent-to-child transfer via props, which is simple and straightforward. However, as the app grows more complex, this becomes a problem because it involves several components using the same state and hence hinders performance and code quality.
This is where Redux, an external state management library comes in. Even so, there was feedback of Redux being
- too complicated to configure
- requiring lots of packages to get even small things done
- too many boilerplate code
A solution was introduced to address these issues, Redux Toolkit (RTK), which is the modern recommended way of writing Redux logic. It is opinionated, providing a standard default template consisting of a single store, reducers contained in a 'slice' file and immutable update logic. Beautiful right?
In this article, we would represent the basic logic of RTK using a simple Counter project example. Let's dive straight in!
Prerequisites
This isn't for React beginners. For this article, knowledge of React is recommended. Basic knowledge of HTML and CSS is also worthy.
Counter
We would be illustrating the fundamental logic behind RTK, to do this, we would make a Counter application using the Create React App (CRA)
boilerplate.
Set up a React project with the following command:
npx create-react-app counter-app
or
yarn create react-app counter-app
Feel free to clear up all the unnecessary files on the CRA template.
Install Redux Toolkit and React-Redux
Next, of course, we need to install Redux Toolkit itself alongside the react-redux
package
#NPM
npm install @reduxjs/toolkit react-redux
#Yarn
yarn add @reduxjs/toolkit react-redux
Great! Now we have all the necessary dependencies installed on our app.
Create a Store
Now, to set up RTK on our app, we would go to the src
folder and create a new folder called app
.
Right inside the app
folder, we would create a file called store.js
. The store.js
file serves as a single container for all states in the app. Redux Toolkit ensures we use a single store container even though multiple stores are possible.
The store would contain a reducer
object in which we would put the reducers that we create later on.
// store.js
import { configureStore } from "@reduxjs/toolkit";
export const store = configureStore({
reducer: {
}
})
Make the Store Available to the Global State
Moving forward, we need to make our store available to all React components via the Provider
object. So we would import our store.js
file and Provider
object in the index.js
.
Afterwards, we have to wrap our entire app with the Provider
object and pass the store as a prop.
// 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 Redux Slice
Part of the logic behind how RTK works is that it creates small state objects known as 'slices'. Think of it metaphorically like a slice of bread from a loaf. However, in this case, each slice contains a single logic that represents a feature in the app. The logic consists of reducer(s) containing actions which are both exported.
To create a slice, we would add a new folder called features
, inside this, we would create another folder called counter
(in this case). This counter
folder would contain a 'slice' for a particular feature. Hence, add a file called counterSlice.js
just inside it. We need to use the createSlice
API from redux toolkit to officially create a slice.
// counterSlice.js
import { createSlice } from '@reduxjs/toolkit';
Afterwards, we need to set an initial state object which we will set to zero. Now we can proceed to define our slice using the createSlice
API, this will consist of some properties including a reducer
object that contains some actions
. The actions
in this case are increment
and decrement
.
These actions are mutating logic in the reducer
object, however, since RTK uses the Immer Library, the state is not being mutated or changed because the library puts the new changes in a 'draft state' and updates a brand new immutable state from the changes made.
// state object
const initialState = {
count: 0
}
export const counterSlice = createSlice({
name: 'counter',
initialState,
reducers:{
// actions
increment: (state) => {
state.count += 1;
},
decrement: (state) => {
state.count -= 1;
},
}
});
Now we need to export our actions
and reducer
, so we can utilize them.
// export actions
export const { increment, decrement } = counterSlice.actions;
// export the full reducer
export default counterSlice.reducer;
Add Slice to the Store
Back to the store.js
, we need to import the reducer
from the slice and place it in our reducer object which was empty before.
PS: You can add more slices to the reducer object, each separated by a comma.
// store.js
import { configureStore } from "@reduxjs/toolkit";
import counterReducer from '../features/counter/counterSlice';
export const store = configureStore({
reducer: {
counter: counterReducer,
}
})
Use the Redux State in the Components
Great work so far!
We can now use the useSelector
and useDispatch
hooks to reference the state and dispatch actions respectively. Therefore, we will create a file called Counter.js
inside the src>features>counter
directory.
import React from 'react';
// importing hooks from react-redux
import { useSelector, useDispatch } from 'react-redux';
// importing actions from slice
import { increment, decrement } from './counterSlice';
const Counter = () => {
// using our state
const count = useSelector((state)=> state.counter.count);
const dispatch = useDispatch();
return (
<div>
<div>
{count}
</div>
<div>
<button onClick={() => dispatch(increment())}>
+
</button>
<button onClick={() => dispatch(decrement())}>
-
</button>
</div>
</div>
)
}
export default Counter;
At this stage we will import and render the Counter
component in the App.js
, this will display it on the user interface.
Anytime you click on the "Plus" or "Minus" button, the corresponding redux action would be dispatched to the store, and then the counter slice reducer will receive the actions and update its state, finally, the Counter.js
component will see the new state value from the store and re-render itself with the new data.
Check out the live demo on the Codesandbox link below.
codesandbox.io/s/sparkling-brook-w97wli
This is a basic illustration of the Redux Toolkit logic and setup. More actions can be added to the reducer for even more complex features such as increasing the counter by a certain amount, a reset back to zero and so on.
Conclusion
Redux Toolkit is a great way to manage your React app's state because it provides that opinionated and easy-to-set-up logic. In addition, it is lightweight and designed originally for React, though it can be used with other frameworks like Angular and Vue. For a more in-depth tutorial, check out the RTK Official Documentation.
That's a wrap and I hope you would be comfortable enough to try out even more projects using Redux Toolkit. Please follow me on Twitter.