React Redux Walk-through
Vinay P
Posted on May 23, 2022
Redux is a state management tool that provides a way to have all the states of an application in one global place instead of having in each components.
Prerequisites:
- React project setup -
npx create-react-app app_name
- Packages -
npm i redux react-redux @reduxjs/toolkit
Methods used:
import { combineReducers, createStore, applyMiddleware } from "redux";
import { Provider, useSelector, useDispatch } from "react-redux";
import { configureStore } from "@reduxjs/toolkit";
1. combineReducers
- Used to combine multiple reducers and make it one to send it to the store.
const reducer = combineReducers({
userData: userReducer,
...
<more here>
});
2. createStore (deprecated)/configureStore
- Used to create a store (redux state) using the combined reducer and also apply any middlewares if required to perform before the actions reaches the reducers.
const store = createStore(reducer, applyMiddleware(thunk, middelware2, middleware3, etc..)); // this is deprecated.
(OR)
const store = configureStore({
reducer,
middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(middleware1, middleware2, etc...)
});
3. Provider
- This component will wrap around the final component (in index.js) that is added to the DOM.
- The store is made available to the entire application through this.
ReactDOM.render(
<Provider store={store}>
<App></App>
</Provider>,
document.getElementById("root")
);
4. useSelector
- Used to access the state data from redux state.
const App = () => {
const userData = useSelector((state) => state.userData); // where userData is the key name given in the combineReducers.
const userId = userData.userId;
return (
<div>
<h3>User id is {userId}</h3>
</div>
);
};
5. useDispatch
- Used to dispatch the actions that will inturn modify the state data accordingly.
const App = () => {
const dispatch = useDispatch();
const updateUserId = (ev) => {
dispatch(setUserId(ev.target.value)); // action creator
dispatch({type: "SET_USER_ID", payload: ev.target.value}); // regular dispatch
};
return (
<div>
<input type="text" value={userId} onChange={updateUserId} />
</div>
);
};
Steps:
1. Declare the action type constants.
~/src/state/actionTypes.js
const actionTypes = {
user: {
SET_NEW_USER: "SET_NEW_USER",
SET_USER_ID: "SET_USER_ID"
},
counter: {
INCREASE_COUNT: "INCREASE_COUNT",
},
};
export default actionTypes;
2. Declare the reducers.
~/src/state/reducers/counterReducer.js
import actionTypes from "../actionTypes";
const counterReducer = (state = 0, action = {}) => {
switch (action.type) {
case actionTypes.counter.INCREASE_COUNT:
return state + action.payload.count;
default:
return state;
}
};
export default counterReducer;
~/src/state/reducers/userReducer.js
import actionTypes from "../actionTypes";
const userReducer = (state = { userId: 1, userData: {} }, action = {}) => {
switch (action.type) {
case actionTypes.user.SET_NEW_USER:
return {
...state,
userData: { ...action.payload },
};
case actionTypes.user.SET_USER_ID:
return {
...state,
userId: action.payload,
};
default:
return state;
}
};
export default userReducer;
3. Combine the reducers.
~/src/state/reducers/index.js
import { combineReducers } from "redux";
import userReducer from "./userReducer";
import counterReducer from "./counterReducer";
const reducer = combineReducers({
userData: userReducer,
countData: counterReducer,
});
export default reducer;
4. Create the store by passing the combined reducer and the middlewares if any.
~/src/state/store.js
// import { createStore, applyMiddleware } from "redux";
// import thunk from "redux-thunk";
import { configureStore } from "@reduxjs/toolkit";
import reducer from "./reducers/index";
// createStore is deprecated..
// const store = createStore(reducer, applyMiddleware(thunk));
const store = configureStore({
reducer,
middleware: (getDefaultMiddleware) => getDefaultMiddleware(),
});
// To add additional middlewares,
// middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(middleware1, middleware2, etc...), // thunk is inbuilt in @reduxjs/toolkit
export default store;
5. Declare the actions as per requirment for the features.
~/src/state/actions/counterActions.js
import actionTypes from "../actionTypes";
const increaseCount = (countToIncrease) => {
return {
type: actionTypes.counter.INCREASE_COUNT,
payload: { count: countToIncrease },
};
};
export { increaseCount };
~/src/state/actions/userActions.js
import axios from "axios";
import actionTypes from "../actionTypes";
const getUser = (userId) => {
return async (dispatchMethod, getState) => {
const userResponse = await axios.get(`https://jsonplaceholder.typicode.com/users/${userId}`);
const userData = await userResponse.data;
dispatchMethod({ type: actionTypes.user.SET_NEW_USER, payload: userData });
return userData;
};
};
const setUserId = (userId) => {
return {
type: actionTypes.user.SET_USER_ID,
payload: userId,
};
};
export { getUser, setUserId };
6. Implement the features and dispatch the actions as required.
~/src/components/counter/Counter.js
import { useSelector, useDispatch } from "react-redux";
import { increaseCount } from "./../../state/actions/counterActions";
const Counter = () => {
const counterData = useSelector((state) => state.counterData);
const dispatch = useDispatch();
const handleIncreaseCount = (count = 1) => {
dispatch(increaseCount(count));
};
return (
<div>
<h2>Counter</h2>
<h3>{counterData}</h3>
<button
onClick={() => {
handleIncreaseCount(1);
}}
style={{ marginRight: "10px" }}
>
Increase count by 1
</button>
<button
onClick={() => {
handleIncreaseCount(5);
}}
>
Increase count by 5
</button>
</div>
);
};
export default Counter;
~/src/App.js
import { useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { getUser, setUserId } from "./state/actions/userActions";
import Profile from "./components/user/Profile";
import Counter from "./components/counter/Counter";
const App = () => {
const userData = useSelector((state) => state.userData);
const userId = userData.userId;
const [showLoader, setShowLoader] = useState(false);
const dispatch = useDispatch();
const getUserInfo = () => {
setShowLoader(true);
dispatch(getUser(userId))
.then((res) => {
console.log(res);
})
.catch((err) => {
console.log(err);
})
.finally(() => {
setShowLoader(false);
});
};
const updateUserId = (ev) => {
dispatch(setUserId(ev.target.value));
};
return (
<div>
<input type="text" value={userId} onChange={updateUserId} />
<h3>User id is {userId}</h3>
<button onClick={getUserInfo}>Get user info</button>
<br />
{showLoader ? "loading..." : <Profile></Profile>}
<hr />
<Counter></Counter>
</div>
);
};
export default App;
7. Wrap the whole application with the provider and pass the store as a prop.
~/src/index.js
import ReactDOM from "react-dom";
import App from "./App";
import { Provider } from "react-redux";
import store from "./state/store";
ReactDOM.render(
<Provider store={store}>
<App></App>
</Provider>,
document.getElementById("root")
);
Persisting redux state on refresh
- This requires changes in the following two files,
-
~/src/state/store.js
-
~/src/index.js
Steps:
1. npm i redux-persist
2. ~/src/state/store.js
// NEW LINES START
import { persistStore, persistReducer } from "redux-persist";
import storage from "redux-persist/lib/storage";
// Setup persist config and persist reducer
const persistConfig = {
key: "persisted_state_data",
storage,
};
const persistedReducer = persistReducer(persistConfig, reducer);
// NEW LINES END
// UPDATED LINE START
// Pass this persisted reducer to the store configurator and disable the serializableCheck.
const store = configureStore({
reducer: persistedReducer,
middleware: (getDefaultMiddleware) => getDefaultMiddleware({
serializableCheck: false,
}),
});
// UPDATED LINE END
// NEW LINE START
// Create a persisted store along with the normal store and export both.
const persistor = persistStore(store);
// NEW LINE END
// UPDATED LINE START
export { persistor, store };
// UPDATED LINE END
3. ~/src/index.js
// Import persisted store and the regular store.
import { persistor, store } from "./state/store";
import { PersistGate } from "redux-persist/integration/react";
// Within the redux provider wrap the app with the PersistGate component and pass the persistStore.
ReactDOM.render(
<Provider store={store}>
<PersistGate persistor={persistor}>
<App></App>
</PersistGate>
</Provider>,
document.getElementById("root")
);
Repo link: Github
P.S.
- First post ๐
- Hopefully it saves someone a few hours of R&D.
๐ ๐ช ๐
๐ฉ
Vinay P
Posted on May 23, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.