Let's understand Redux in simple 4 steps by making a counter application
The first thing we need to know is that Redux basically follows three main principles,
- A complete application will have a single state tree or object tree. The advantage is that the application can be easily debugged and it ensures a single source of truth for the data.
- The state will be read-only which can only be changed by doing the action dispatch. That means sometimes if someone needs to change the state they will just dispatch the specific action. Here action is a simple JavaScript object that holds all the information about that action like what type of action will be, what data needs to be changed etc.
- After dispatching a particular action, the state needs to be changed through the necessary pure functions called reducers in Redux.
Now let’s see how we will use redux. Suppose we want to make a small counter application, Where we can show the count, we can control the increment degree separately and keep the history at the same time, if we want we can clear the count or history separately or with one click.
So to make this application in React, our component will be like the picture below.
Step 1 — Installing required libraries
yarn add redux react-redux
Step 2 — Create Actions, Action Types, Action Creators, Reducer and Store
let's create a store.js file inside of the src folder. The Redux Store holds the state of the app. The app is always subscribed to the store.
store.js contain the following code,
import { combineReducers, createStore } from 'redux';
// Action Type
export const DECREMENT = 'decrement';
export const INCREMENT = 'incriment';
export const CLEAR_COUNT = 'clear-count';
// actions
// Action types are constants that are used to define the values used for the type property of Actions.
// An action is any object with a type property. Actions help us describe what needs to happen.
// also actions contain payload
// Action Creator
// An action creator is simply a JS function that returns an action.
export const incriment = (payload) => ({
type: INCREMENT,
payload,
});
export const decriment = (payload) => ({
type: DECREMENT,
payload,
});
export const clearCount = () => ({
type: CLEAR_COUNT,
});
// reducer
// A reducer is a JS function that takes in initialState and action as input and returns an updated state object.
// When an action is dispatched, the relevant reducer will update the state according to the action.
const countReducer = (/* previous State */ state = 0, action) => {
// processing area
switch (action.type) {
case INCREMENT:
return state + action.payload;
case DECREMENT:
return state - action.payload;
case CLEAR_COUNT:
return 0;
default:
return state;
}
// returned stste is next state
};
// actions
const ADD_TO_HISTORY = 'addToHistory';
const CLEAR_HISTORY = 'clearHistory';
let id = 1;
function generateId() {
return id++;
}
function getTime() {
const t = new Date();
return t.toLocaleTimeString();
}
// Action Creator
export const addHistory = (history) => ({
type: ADD_TO_HISTORY,
payload: {
id: generateId(),
action: history.action,
count: history.count,
time: getTime(),
},
});
export const clearHistory = () => ({
type: CLEAR_HISTORY,
});
const historyReducer = (state = [], action) => {
switch (action.type) {
case ADD_TO_HISTORY:
return [...state, action.payload];
case CLEAR_HISTORY:
return [];
default:
return state;
}
};
const store = createStore(
combineReducers({
count: countReducer,
history: historyReducer,
})
);
export default store;
Feeling Overwhelmed?
Don't worry, we just finished the most complex part of our app.
if you want you may divide the given store.js into chunks of code into different file like actions.js, actionCreator.js, reducer.js, and store.js in my case I’m doing all a single store.js
Step 3 — Wrap the Root Component with the Provider
in main.jsx (vite create react)
<Provider store={store}>
<App />
</Provider>
Step 4 — useDispatch() and useSelector()
You can use useSelector() to access the states in the Redux store.
You can use useDispatch() to access the dispatch method which helps you to dispatch actions
let's do that,
now create a components folder inside of src, then create count.jsx, IncrementButton.jsx, DecrementButton.jsx, History.jsx and ClearHistory.jsx file inside of your components folder
write the following code into count.jsx
import { useSelector } from 'react-redux';
const Count = () => {
const count = useSelector((state) => state.count);
console.log(count);
return (
<div>
<h1>Count: {count}</h1>
</div>
);
};
export default Count;
write the following code into IncrementButton.jsx
import { useDispatch } from 'react-redux';
import { INCREMENT, addHistory, incriment } from '../store';
const IncrimenntButton = () => {
const dispatch = useDispatch();
const handleClick = () => {
dispatch(incriment(1));
dispatch(addHistory({ action: INCREMENT, count: 1 }));
};
return <button onClick={handleClick}>+</button>;
};
export default IncrimenntButton;
write the following code into DecrementButton.jsx
import { useDispatch } from 'react-redux';
import { INCREMENT, addHistory, incriment } from '../store';
const IncrimenntButton = () => {
const dispatch = useDispatch();
const handleClick = () => {
dispatch(incriment(1));
dispatch(addHistory({ action: INCREMENT, count: 1 }));
};
return <button onClick={handleClick}>+</button>;
};
export default IncrimenntButton;
write the following code into History.jsx
import { useSelector } from 'react-redux';
const History = () => {
const history = useSelector((state) => state.history);
return (
<div>
{history.length > 0 ? (
<h4>
Histories:
<ol>
{history.map((item) => (
<li key={item.id}>
{item.action} - {item.time}
</li>
))}
</ol>
</h4>
) : (
<h4>There is no histories yet!</h4>
)}
</div>
);
};
export default History;
write the following code into ClearHistory.jsx
import { useDispatch } from 'react-redux';
import { CLEAR_COUNT, addHistory, clearCount, clearHistory } from '../store';
const ClearHistory = () => {
const dispatch = useDispatch();
const handleClearHistory = () => {
dispatch(clearHistory());
};
const handleClearCount = () => {
dispatch(clearCount());
dispatch(addHistory({ action: CLEAR_COUNT }));
};
return (
<div>
<button onClick={handleClearCount}>Clear Count</button>
<button onClick={handleClearHistory}>Clear History</button>
</div>
);
};
export default ClearHistory;
now combines this into App.jsx
import './App.css';
import ClearHistory from './components/ClearHistory';
import Count from './components/Count';
import DecrimentButton from './components/DecrimentButton';
import History from './components/History';
import IncrimenntButton from './components/IncrimenntButton';
function App() {
return (
<>
<Count />
<div>
<IncrimenntButton />
<DecrimentButton />
<ClearHistory />
</div>
<History />
</>
);
}
export default App;
That’s about it. We successfully added Redux to a React app.
If you would want to check over the code and give it a try, here is the Code Sandbox version.
Check Live
In Conclusion,
It may seem challenging at first to integrate Redux into your React application. But when we take each idea individually and work out the reasoning on our own, it becomes much easier.
I’m still trying to study these ideas on my own. Please don’t hesitate to point out any errors in my logic and the issues you run across.
I’m appreciative that you read my piece. Please remember to like and share the post with your fellow developers.