Managing isomorphic state with React.js and Redux

Phil Parsons

Tagged: , ,

There has been a lot of setup covered in previous articles and now it’s time to introduce the application that we will be building throughout the rest of this series, an online support management platform. In these early articles we are going to focus on building a chat widget that can be placed on to an existing website to chat to a member of a support team. In later articles we will build the admin interface for the platform that allows administrative users to manage multiple discussions simultaneously.

This post is part of the Developing for a modern web with React.js series. If you’d like to be notified of new posts in this series you can become a free member. If you don’t want to complete the previous articles before working through this tutorial you can download the code from Github.

The chat widget has a text field for composing a message and a list above the text field to display the messages. From this description of the product so far we can identify two components that we need to create with React.js. The list of messages (MessageList) and the text input (MessageEntryBox). Let’s build these first two components starting with the MessageList. Write and save the following code to ./client/components/message-list/index.js.

The MessageList component expects a list (a JavaScript array) of messages in it’s properties named messages. In the render method of this component an ordered list (<ol>) is returned within which each message in the list is displayed as a list item (<li>). A message is a JavaScript object with a property named text that holds the content of the message and an id. We supply a key to each list item containing the message id so that React is able to reconcile them on each update. You can read about how React handles updating dynamic children to find out more about this. Next create the MessageEntryBox component and save it to ./client/components/message-entry-box/index.js.

The MessageEntryBox component renders a textarea for the user to type each message. To the textarea we provide a name, value and two event handlers. For the event handlers onChange and onKeyPress we pass methods that are bound to the current this context with .bind(this). We do this so that when the method is called the scope of the method is correctly set to the component instance. React.js normalizes DOM events so that they behave the same way across browsers. Each time the value of the textarea changes the handleChange method runs as does the handleKeyPress method each time the user presses a key inside of the textarea.

In the handleChange and handleSubmit methods we expect the onChange and onSubmit functions to exist in the components properties. These functions are to be passed into the MessageEntryBox component by a top level component. The App component will be responsible for passing in these functions and creating actions that update the state of the application. In the handleChange method this.props.onChange is called and passed the new value of the field. The handleKeyPress method checks if the pressed key is the enter key (the enter key has a code of 13) and if true calls this.props.onSubmit without any additional arguments.

Using Redux for application state

With the addition of the MessageEntryBox and MessageList components state is being introduced into the application. To manage this state we are going to use Redux which is a small set of tools that enable us to control our application state in a Flux like architecture. Flux is an architectural concept where data flows unidirectionally within an application. In Redux we use Actions, Reducers and a Store to manage the state. We will introduce these component parts properly as we implement them but right now let’s install the Redux dependencies for working with React.js.

Once these dependencies are installed make the following changes to the ./client/components/app/index.js component file to use the new components just created.

The new components are imported at the top and used in the render method. The properties required by these new components (messages, currentMessage) and the event handler methods (onChange and onSubmit) are expected to be present in the App component props but currently are not. We will add them shortly but first we need to introduce Redux.

In Redux the store manages the application state and provides a basic API to allow updating the state, fetching the state and subscribing to changes in the state. We will be using the react-redux package which provides some additional tools for working with Redux in a React.js application. Let’s start by making an update to the server code in ./server/index.js to include Redux and create the initial state of the chat widget.

The createStore utility is imported from Redux and the Provider component from react-redux. At the top of the route handler function we create an object named initialState that contains an empty list of messages and an empty string for the currentMessage. We use createStore to create a Redux store and for now pass it an arrow function that does nothing but return the initial state. The Provider component now wraps our App and provides access to the store so that we can connect the App component to it. A JSON string representation of initialState is created and passed as a parameter to the app template so that the state can be shared with the client. Add the JSON string to the ./server/views/app.handlebars template as below.

The initial state is shared by defining a global variable named INITIAL_STATE on the window object. This variable can be referenced in our client code to create a store in the same way as the server. Update ./client/index.js to create the store and render the App inside a Provider component.

For now we create the store on the client with an arrow function that returns the initial state in the same way as we did for the server. What we need to do is create a reducer that can respond to the actions to update the current message and to add that message to the list. Before adding these action creators connect the App component to the store. This is done using connect from the react-redux package. Edit ./client/component/app/index.js to use connect.

connect is a function; we call connect and pass two parameters that are also functions. The first function mapStateToProps injects values from the state in the store to the components properties. The second function mapPropsToDispatch injects action creator functions into the component properties that dispatch the returned action objects to the store. The returned value from calling connect is another function that takes a component as an argument. When we call this function with the App component class a new component class is returned, a component that is connected to the store.

If this line of code and the above explanation is confusing you may want to break it down and think of it as the following (which works just as well).

The connected App component already has the currentMessage and messages passed in from mapStateToProps but still needs the updateMessage and addMessage action creators. Let’s create them by writing and saving the following code to ./client/actions/message-actions.js.

Action creator functions return an object with a type and any additional data attributes that are required to update the state in the store. This can be seen in the updateMessage action creator function which takes a parameter named message, the value from the text input field in the MessageEntryBox component. Add the action creators to the mapDispatchToProps function in the App component now using bindActionCreators from Redux.

The action creator functions are imported at the top of the file using the import * as messageActionCreators syntax. This will import all of the exported action creator functions and constants in the message-actions module and make them available in an object named messageActionCreators. bindActionCreators binds the action creator functions to the connected App component properties dispatching the action objects returned from those functions to the store when they are called.

We now have actions being created and dispatched to the store but we don’t have any logic to respond to these actions and update the state. Reducer functions are what we use to do this so let’s create a reducer and save to ./client/reducers/index.js.

This module returns a function that takes the initial state of the application as it’s only parameter. When called the function returns another function that is a reducer which takes the previous state as the first parameter and an action as the second. The switch statement inside this reducer function handles the ADD_MESSAGE and UPDATE_MESSAGE action types. For the ADD_MESSAGE action the value stored in the currentMessage field is trimmed and added as a new message to the messages list (if it’s not an empty string after trimming) and the value of currentMessage is reset. We return a new copy of the messages array so that we do not mutate the previous state of the application. For the UPDATE_MESSAGE action the value in currentMessage is updated. If the dispatched action type does not match any of the cases then the previous state is returned untouched.

Swap the function in the createStore call in ./client/index.js to use the reducers.

We are nearly ready to build and run the application but we have one last thing to do before we can get a successful build. Update the webpack.config.js file to tell webpack where to find the action creators and reducers.

Build the application with webpack from the root of the project folder. Fix any syntax errors you may have introduced if they appear before running babel-node index.js from inside the server directory. Open the application in the browser at http://localhost:3000 and you should see a lonely text box where the message from the last article used to live. As you type into the box you should see the value of the field update, hit enter and the message should be added to the message list above clearing the text box for the next message.

There was a lot of upfront coding during this tutorial before we were in a state where we could build and run the application. When making changes during the development process it soon becomes annoying to have to keep manually running webpack to build the new code before stopping and starting the application from the server directory. In the next article we will introduce the webpack development server to watch our file system and automatically build our code changes for us, hot loading those changes into the running application. We’ll also set up the Redux development tools and nodemon for restarting the application server.

This post is part of the Developing for a modern web with React.js series. If you enjoyed the tutorial and would like to follow along to future posts please sign up to become a free member.

It's only fair to share...Tweet about this on TwitterShare on FacebookShare on Google+Buffer this pageShare on LinkedInPin on Pinterest

/ 9 Articles

Phil Parsons

9 Comments

  1. wallebot

    Hi,

    love these articles – some of the best online on this topic at the moment (especially the server-rendering). Am collecting your setup as a boilerplate on my github for future projects (please let me know if can give more recognition to your construction). This week does not apply however will likely add the upcoming article’s webpack-dev-server implementation.

    https://github.com/Cryptolemming/webpack-server-boilerplate

    Cheers 🙂

    Reply

    • Phil Parsons

      Thanks, feel free to attribute these articles to your own work.

      Reply

  2. Jay

    Great series! Will you add react-router next?

    Reply

    • Phil Parsons

      Thank you.

      We will be introducing redux-router in later articles when we build the admin interface The next couple of articles will cover development tooling with HMR (Hot Module Replacement) and real time data updates with RethinkDB.

      Reply

  3. Andy

    Man, JS is so complicated. Great articles, though, Phil. Thank you very much for them.

    Reply

  4. Dan

    Being isomorphic, this code set should also work without javascript enabled right? so we should be able to add to the list with page refreshes? – it doesn’t but would that be the end goal?

    Reply

    • Phil Parsons

      No, you could definitely achieve that if you wanted to but that is not the intention for these tutorials.

      Reply

  5. denis

    Hi again,

    in you explaination text you wrote “…The second function mapPropsToDispatch injects … ” but inside the code section the function is named “mapDispatchToProps”

    This comment here doesn’t need to be displayed in the comments…

    Reply

Trackbacks & Pingbacks

  1. Managing isomorphic state with React.js and Redux – Spraso – Redux

Leave a Comment

Your email address will never be published or shared and required fields are marked with an asterisk (*).