React: A Simple Introduction to Redux

4/12/2021 5:00:44 PM

React and Redux can get pretty complicated and appears very complicated at the beginning. The following is an attempt to create a very simply introduction to adding Redux to your React app. We will create a class component and a functional component using the Redux store.


Add the following packages to package.json and run npm install.

"react-redux": "7.2.3",
"redux-thunk": "2.3.0",
"redux": "4.0.5",

Update index.js with the following imports and render.

//redux
import { Provider } from "react-redux";
import store from "./redux/store";


ReactDOM.render(
    <React.StrictMode>
        <Provider store={store}>
            <App />
        </Provider>
    </React.StrictMode>,
    document.getElementById('root')
);

Update App.js with a component that will use the redux store

import Users01 from "./components/users01";
import Users02 from "./components/users02";

function App() {
  return (
    <div className="App">
      <h1>React Testing</h1>
        <Users01 />
		<hr />
		<Users02 />
    </div>
  );
}

export default App;

Create /components/users01.js (functional component)

import React, { useEffect } from 'react';

//redux
//useSelector, useDispatch only works in functional components
import { useSelector, useDispatch } from "react-redux";
import { users_list, users_add } from "../redux/actions/usersActions";


export default function Users01()
{
    //this is the item in the store you are using (state.users.items)
    const users = useSelector(state => state.users.items);

    //this is used to call an action (populate the users in this case)
    const dispatch = useDispatch();


    useEffect(() =>
    {
        //on initial load, call the users list action
        dispatch(users_list());
    }, []);


    return <>
        <button onClick={() => dispatch(users_add())}>Add User</button>
        <div className="users">
            {users.map((item, index) => (
                <h1 key={item.name}>{item.name}</h1>
            ))}
        </div></>
}

Create /components/users02.js (class component)

import React, { Component } from 'react';

//redux
import { connect } from "react-redux";
import { users_list, users_add } from "../redux/actions/usersActions";


class Users02 extends Component
{
    async componentDidMount()
    {
        //this doesn't have to be called since it is being called in the functional component and the state is shared
        //this.props.dispatch(users_list());
    }

    render()
    {
        //this is made possible be mapStateToProps
        const { users } = this.props;

        return <>
            <h1>Users: 02</h1>
            <button onClick={() => this.props.dispatch(users_add())}>Add User</button>
            <div className="users">
                {users.map((item, index) => (
                    <h1 key={item.name}>{item.name}</h1>
                ))}
            </div></>
    }
}


//these are the state properties you want access to
//these will get added to this.props
const mapStateToProps = state => ({
    users: state.users.items
});


export default connect(mapStateToProps)(Users02);

Redux Store Functions


Create /redux/store.js

import { createStore, applyMiddleware } from "redux";
import thunk from "redux-thunk";
import rootReducer from "./reducers"; //webpack automatically checks index.js

const middleware = [
    thunk,
];

const store = createStore(
        rootReducer,
        applyMiddleware(...middleware)        
);

export default store;

Create /redux/actions/types.js

export const USERS_APPEND_ITEM = "USERS_APPEND_ITEM";
export const USERS_LIST_SUCCESS = "USERS_LIST_SUCCESS";

Create /redux/actions/userActions.js

import * as ActionTypes from "./types";


export function users_add()
{    
    let new_user = {
        name: Math.floor(Math.random() * 10000001) + 1,     // returns a random integer from 1 to 10000000                
    };

    return {
        type: ActionTypes.USERS_APPEND_ITEM,
        payload: new_user
    }
}


export function users_list()
{    
    //this async function is made possible by the thunk middleware
    //you will often need an async function if you are making an ajax request
    return async function (dispatch)
    {
        let items = [];
        
        //populate your items - normally would be an ajax call
        
        let number_of_users = 10;
        for (let x = 0; x < number_of_users; x++)
        {
            let user = {
                name: Math.floor(Math.random() * 10000001) + 1,     // returns a random integer from 1 to 10000000                
            };

            items.push(user);
        }

        //dispatch this action to redux reducers
        dispatch(
            {
                type: ActionTypes.USERS_LIST_SUCCESS,
                payload: items
            }
        );
    }
}

Create /redux/reducers/index.js

//combine the reducers
import usersReducer from "./usersReducer";

import {combineReducers} from "redux";


const rootReducer = combineReducers({
    users: usersReducer
});


//if not using specific names
// const rootReducer = combineReducers({
//     usersReducer
// });


export default rootReducer;

Create /redux/reducers/usersReducer.js

import * as ActionTypes from "../actions/types";

const initialState = {
    item: null,
    items: [],
    loading: false,
    error: null
}

const usersReducer = (state = initialState, action) =>
{
    switch (action.type)
    {
        case ActionTypes.USERS_LIST_SUCCESS:
            {
                var new_items = [];

                //merge with previous list???
                if (state.items)
                {
                    new_items = [...state.items, ...action.payload];
                }
                else
                {
                    new_items = action.payload;
                }


                return {
                    ...state,
                    loading: false,
                    items: new_items
                };
            }

        case ActionTypes.USERS_APPEND_ITEM:
            {
                var new_items = [];

                //merge with previous list
                if (state.items)
                {
                    new_items = [...state.items, action.payload];
                }
                else
                {
                    new_items = [action.payload];
                }


                return {
                    ...state,
                    items: new_items
                };
            }

        default:
            return state;
    }
}

export default usersReducer;