Skip to content
This repository was archived by the owner on Aug 3, 2018. It is now read-only.

4. Add Redux

Wiley Hilton edited this page Mar 30, 2018 · 1 revision

What is Redux?

Redux is a state engine. A state is essentially a giant object that contains a lot of information the client may need or may edit at certain point all stored on the clients machine. You can read more about redux here.

Installing Redux

To install redux to your project, just like any other npm package, you use npm i. In this case we need two packages, redux and react-redux. run npm i --save redux react-redux to install these packages.

Redux Files

There are two main files that redux relies on: Actions and Reducers. Reducers are sections of the state that can be edited and actions are what's applied to those reducers to edit the state.

Redux Setup

Create the two main files ~/Content/actions.js and ~/Content/reducers.js. In the reducers we'll need to setup the shape of the state. For now we'll just use a blank state to ensure everything is working. The reducer file should look like this:

import { combineReducers } from 'redux'; // The redux function that creates the store

// An Empty store
export default combineReducers({});

Back at base.jsx you'll need to create a link to this store we just created. Add these lines to the top of the base.jsx file:

import { createStore } from 'redux';
import { Provider } from 'react-redux'
import app from './reducers';
let store = createStore(app);

Then lastly update the render function of base.jsx to:

    render() {
        return (
            <Provider store={store}>
                <MessageManager 
                    messages={this.props.messages}
                />
            </Provider>
        )
    }

Additionally I like to have a method I can use to read the current state at any point. Right before the class was created I added:

global.getState = () => store.getState();
class Base extends React.Component {

All together the file now looks like:

var messageArray = [
    {
        from: "Person 1",
        message: "Test Message"
    },
    {
        from: "Person 2",
        message: "Test Message 2"
    },
    {
        from: "Person 1",
        message: "Test Message 3"
    },
];

import { createStore } from 'redux';
import { Provider } from 'react-redux'
import app from './reducers';
let store = createStore(app);

import MessageManager from "./messageManager";
global.getState = () => store.getState();

class Base extends React.Component {
    // This function is required on all React.Components as it's what's called to render the component
    render() {
        return (
            <Provider store={store}>
                <MessageManager 
                    messages={this.props.messages}
                />
            </Provider>
        )
    }
};

// This renders the passed in component to the passed in DOM
ReactDOM.render(
    <Base 
        messages={messageArray}
    />, // Name of the React.Component we just made
    document.getElementById('content') // Remember the div we ID'd 'content' on the _Layout.cshtml page?
);

Once again run webpack and you can see that nothing has changed (but it didn't crash). If you open the chrome/IE developer tools (f12) then go to the console you can run getState() and it should return an empty object to you. You'll also notice two error messages, we'll fix those later.

Shape Redux

Now that we have redux setup and working it's time to give it a shape. Currently the only thing we care to track are messages. In order to track anything with redux we have to setup a reducer for it. Update the reducer.js file to:

import { combineReducers } from 'redux'; // The redux function that creates the store

// This reducer will control all the messages
const messages = (
    state = [], // This sets the default state of this reducer to an empty array
    action // This is the action that's being applied to the store
)  => {
    // If the action that's applied to the reducer don't affect this reducer, you still must return the current state
    return state; 
}

// An Empty store
export default combineReducers({
    messages // This adds the messages reducer to the store
});

If we were to webpack, run, and getState() we would see {messages:[]}.

Redux Actions

Now that we have a reducer, we need to define actions that can be applied to that reducer. In this example we'll be using the following actions:

  • Add
  • Delete

Knowing this we can start working on the actions.js file. Go ahead and put this in it:

// These are unique strings that are used to determine what action is being applied
// We use these because there's no way for us to know what action to perform without a unique identifier
// Realistically we could use an index number, but strings are easier to understand
export const actions = {
    ADD_MESSAGE: "Adds a message to the redux store",
    DELETE_MESSAGE: "Removes a message from the redux store"
};

/**
 * Adds the passed in message to the redux store
 */
export const addMessage = msg => {
    return {
        type: actions.ADD_MESSAGE, // type should be unique for each action being performed
        msg // The other properties could be anything you want
    }
}

/**
 * Removes the passed in ID from the redux store
 */
export const deleteMessage = id => {
    return {
        type: actions.DELETE_MESSAGE,
        id
    }
}

Update Reducers

Now that we've defined the actions, we have to update the reducers.js one more time. Start by importing the "actions" enum via import {actions} from './actions';. Then update the messages reducer to:

    // Remember how the required property in the actions.js file was "type"? here it is
    switch(action.type) { 
        case actions.ADD_MESSAGE:
            return [...state, action.msg]; // Here's the other property, msg, which we've defined in the actions.js file

        case actions.DELETE_MESSAGE:
            return state.filter(msg => msg.id !== action.id);
    }

    // If the action that's applied to the reducer don't affect this reducer, you still must return the current state
    return state; 

Link to React

Now that the reducers and actions are all setup, we need to add a link to them from the react components. Navigate to the messageManager.jsx file and add the following to the bottom of the page (while also deleting the export line):

// Here you are able to get specific reducer values from the store
const mapStateToProps = state => {
    return {
        messages: state.messages
    }
};

// Here you can say which actions you want to be able to perform from this component
const mapDispatchToProps = dispatch => {
    return {
        add: msg => dispatch(addMessage(msg)),
        delete: id => dispatch(deleteMessage(id))
    }
};

export default connect(mapStateToProps, mapDispatchToProps)(MessageManager);

You will also need to add import {connect} from 'react-redux'; to the top of the page as well as the two actions we're going to be using: import { addMessage, deleteMessage } from './actions';.

Initiate Store using Actions

We want to initiate the store with some data when we first load the application, so add a method to the MessageManager component called componentDidMount. This is a build in React function that runs after the first time the render function runs. That method should look like:

    componentDidMount() {
        this.props.add({
            from: "1",
            message: "Test Message"
        });
        this.props.add({
            from: "2",
            message: "Test Message 2"
        });
        this.props.add({
            from: "1",
            message: "Test Message 3"
        });
    }

Update base.jsx(Again)

Now that MessageManager is getting it's data from the redux store you'll need to remove the messages prop from the base.jsx file. Back at base.jsx you can now delete the messagesArray from the top of the page. Also at the bottom of the page you can remove the messages={messagesArray} line from the ReactDOM.render function. Lastly you can remove the messages={this.props.messages} line from the Base.render function. After all those changes it should look like this:

import { createStore } from 'redux';
import { Provider } from 'react-redux'
import app from './reducers';
let store = createStore(app);

import MessageManager from "./messageManager";
global.getState = () => store.getState();

class Base extends React.Component {
    // This function is required on all React.Components as it's what's called to render the component
    render() {
        return (
            <Provider store={store}>
                <MessageManager />
            </Provider>
        )
    }
};

// This renders the passed in component to the passed in DOM
ReactDOM.render(
    <Base />, // Name of the React.Component we just made
    document.getElementById('content') // Remember the div we ID'd 'content' on the _Layout.cshtml page?
);

Compile and Run

Just as always, run webpack to compile the changes and run/refresh the page. You should see the new messages displayed on top of the page. Additionally you can run getState() in the console and you should see the messages are in the redux store.

The commit I made at this point was: 933af8094e633649fe81ed80faaf7ed4ebea55cf

Clone this wiki locally