React Redux Walk-through
Tags: react, redux, webdev, javascript
Published: 2022-05-23 05:48:25

**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](https://github.com/aydotvin/template_redux)
###### P.S.
-   First post :pray:
-   Hopefully it saves someone a few hours of R&D.