DEV Community

Cover image for React Redux Walk-through
Vinay Prasad N
Vinay Prasad N

Posted on

React Redux Walk-through

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>
});
Enter fullscreen mode Exit fullscreen mode
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...)
});
Enter fullscreen mode Exit fullscreen mode
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")
);
Enter fullscreen mode Exit fullscreen mode
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>
    );
};
Enter fullscreen mode Exit fullscreen mode
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>
    );
};
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode
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;
Enter fullscreen mode Exit fullscreen mode

~/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;
Enter fullscreen mode Exit fullscreen mode
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;
Enter fullscreen mode Exit fullscreen mode
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;
Enter fullscreen mode Exit fullscreen mode
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 };
Enter fullscreen mode Exit fullscreen mode

~/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 };
Enter fullscreen mode Exit fullscreen mode
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;
Enter fullscreen mode Exit fullscreen mode

~/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;
Enter fullscreen mode Exit fullscreen mode
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")
    );
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
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")
);
Enter fullscreen mode Exit fullscreen mode


Repo link: Github

P.S.
  • First post 🙏
  • Hopefully it saves someone a few hours of R&D.

Top comments (0)