React + Redux Tutorial


Bisacode - React appears to have won the day when it comes to the JavaScript frameworks. One of React’s many strengths is the library’s narrow focus on only the view. It’s small scope allow’s it’s API to be small and compact making for a relatively small learning curve.
React has a great tutorial on getting started with the library. It uses an example that leverages almost all of the pertinent parts of the API. As a mechanism for learning how to use React, it does a terrific job, but depending on what architecture one intends to use, it may get a bit too much into the weeds on some aspects one’s own particular architecture would generally avoid.
React most often accompanies a particular architecture pattern called Flux, which features a unidirectional data flow around some core concepts. It is a fantastically simple and elegant concept. As terrific as this is, there are a couple of ways React + Flux can present some initial challenges to new comers.
Flux can be thought of as a category of architectures (either that or Flux is just one architecture within a category which is sometimes referred to as “fluxy”) and there are a number of flavors. Depending on which flavor one wants to use, certain parts of React are to be avoided or embraced.
I tend to lean toward immutability, functional purity and simplicity. For these reasons, I really like Redux. Redux is a library for dealing with state and when combined with React take flux down a pretty strong immutable, functional and simple path way.
In this post, I want to take Facebook’s official React tutorial, go a little deeper and apply some Redux thinking as well as a few opinions. Over a few stages, we’re going to produce an app with an architecture and file structure that can easily scale and serve as a template for future projects.

Tutorial

Facebook’s tutorial keeps it as much about React as possible, leaving out build steps and other concerns where ever it can. We’re going to try to follow their lead on that and leave out the cruft.
To that end, and for simplicity’s sake, there is a github repo that comes with a server and an `index.html.` The final version of the project is also included for reference. Just clone the repo and run `npm start.` The project will be viewable at `localhost:4000.` To keep things as simple as we can at first, we will write our initial implementation directly in a script tag in the `index.html`.
The Facebook tutorial builds a chat app, which we have borrowed heavily from. We’re going to build the same thing, but in a little different order, and with some different techniques.
Let’s start with the `index.html` page. This page includes the React and Redux libraries as well as some helper libraries we will discuss as we utilize them that make working with React and Redux easier. There are also some scripts that allow our code to transpile in the browser. This is what allows us to write our app inside a script tag in the HTML without an explicit build step.
<!DOCTYPE html>
<html>
  <head>
    <meta charset=”utf-8">
    <title>React Tutorial</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.6/react.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.6/react-dom.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/redux/3.0.5/redux.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/4.0.6/react-redux.js"></script>
  </head>
  <body>
    <div id="content"></div>
    <!-- uncomment the below script to see final version -->
    <!-- <script type="text/babel" src="scripts/final.js"></script> -->
    <script type="text/babel">
        // Our app code will all go here
    </script>
    <script src="scripts/babel.min.js"></script>
    <script src="scripts/babeler.js"></script>
  </body>
</html>

Hello World

Let’s cut right to the chase. Here is your obligatory `Hello World` React app.
ReactDOM.render(
    <div>Hello World!</div>,
    document.querySelector('#content')
);
The ReactDOM.render method is how a React app is bootstrapped. It takes a single React component as it’s first argument — React can translate many components with HTML-element names to their DOM equivalent at render time, and renders it into the content of the second argument which must be an actual DOM node. It’s that easy.

Your first component

A React application is created by composing components. At it’s simplest, a React component is just a function that returns an object describing the component. Since we are creating a commenting app, let’s start by creating a comment component. We will update our react app to the following.
const Comment = (props) => (
   <div className="comment">
       <h2 className="commentAuthor">
           { props.author }
       </h2>
       { props.children }
   </div>
 );
ReactDOM.render(
    <Comment author="Cory">
        So, what do you think so far?
    </Comment>,
    document.querySelector('#content')
);
You can see here that the `Comment` component is just a function that accepts a `props` argument and returns an object — the almost, but not quite HTML looking business in the parentheses ends up being nothing more than a plain JavaScript object.
For describing UI, React uses JSX — a declarative syntax extension to JavaScript that the transpiler understands and converts to regular javascript for us. It bares a resemblance to XML, but syntax is about the only thing that JSX and XML have in common. JSX is just syntactic sugar for working with JavaScript in a particular way. It’s important to note that it is not a string, nor a template as such, but is in fact a JavaScript object. It is referenceable, extendable, manipulatable. It has properties — ‘className’ is a property on both the ‘div’ and ‘h2’ objects — and methods — we’ll get to that in a bit. It is certainly possible to use React without using JSX, but it is not advisable. JSX handles the responsibility of describing a component’s presentational structure so cleanly that any aversion one may have to it’s syntax is quickly abandoned after a short time using it. Give it a chance and you will quickly see the advantages. If you like, you can rathole on to JSX here at your leisure :)
In addition to the definition of the `Comment` component, we also update the call to `ReactDOM.render` to use the newly minted`Comment` component as our “root” component, and we give it the properties we want passed into it. These are the `props` that are passed to our component’s factory function.

Composing Components

This is all well and good, but we expect to have an unknown number of comments. Moreover, we are likely to receive those comments as data in the form of JSON. How do we get from JSON to markup?
Let’s start by taking a look at our data.
let comments = [
    {id: 1, author: "Cory Brown", text: "My 2 scents"},
    {id: 2, author: "Jared Anderson", text: "Let me put it this way. You've heard of Socrates? Aristotle? Plato? Morons!"},
    {id: 3, author: "Matt Poulson", text: "It's just a function!"},
    {id: 4, author: "Bruce Campbell", text: "Fish in a tree? How can that be?"}
];
You can see, we have a list of comments, each of which have an id, author, and text property. We will need some way to iterate over the comments and construct a `Comment` component for each one. It turns out, unlike many frameworks out there, React doesn’t shy away from JavaScript! There is no secret sauce for iterating over a list, we’re going to use the plain ol’ JavaScript `map` function. We will map over each comment data structure, returning a Comment component for each one.
We will construct a new component to handle the collection. Let’s call it…. oh, I don’t know… `CommentList`.
We will expect an array of comments to be passed in as a `prop` to the CommentList component who’s only responsibility will be to create a Comment component for each comment in the list it receives.
const CommentList = (props) => (
    <div className="commentList">
        { props.comments.map(comment => (
            <Comment author={ comment.author } key={comment.id} >
                { comment.text }
            </Comment>
        )) }
    </div>
);
This is great, but it’s a little busy for my taste. Since there are a very specific set of props our component cares about, I’m going to use JavaScript’s argument destructuring syntax pretty extensively from here on. If you are unfamiliar, I very much encourage you to spend some time and get familiar as it will improve the readability and of your functions (React components or otherwise) as well as provide a high degree of self documentation.
With destructuring, our component now looks like this.
const CommentList = ({ comments }) => (
    <div className="commentList">
        {comments.map(({ author, id, text }) => (
            <Comment author={author} key={id} >{text}</Comment>
        ))}
    </div>
);
Let’s see how this looks by updating our ReactDOM.render method to use the CommentList component rather than a single Comment component.
ReactDOM.render(
    <CommentList comments={comments} />,
    document.querySelector('#content')
);
Notice how we are not restricted to using strings as our property values like we are with attributes on HTML elements. We can pass around any old data structure. Again, JSX is a syntax extension to JavaScript and produces a plain JavaScript object.
Savvy?

User Interactions

Thus far we have created components that are only concerned with presentation and do not dictate behavior.
Components have a lot of flexibility, but it is helpful to classify and restrict components into a few different categories. “Pure” components should only operate on data passed to them through props. They do not retrieve their own data, nor do they dictate behavior. Instead, they rely on “smart” components higher up in the component tree to provide data and behavior to them.
Dictating behavior is the leading cause of reuselessness in React applications
-Dr Knowitall
Pure components are the most reusable. Behavior is often coupled to particular context in which a component is used, the data it is connected to, the actions to perform to change the data, etc. It should be the goal in every React application to maximize the number of dumb components. This maximizes reusability, but it also allows us to consolidate necessary app specific functionality into designated smart components which helps reduce the complexity and maintenance of your application. Smart components have the responsibility of dictating behavior. They may dictate their own behavior, but they also pass behavior down into child components.
A commenting app isn’t very useful if there isn’t a way to comment, so let’s make a CommentForm component and give it some behavior for how users will interact with it. We will start with what we know, a pure component.
const CommentForm = (props) => (
    <form className="commentForm">
        <input type="text"
               name="author"
               placeholder="Your name"
        />
        <input type="text"
               name="text"
               placeholder="Say something..."
        />
        <button>Post</button>
    </form>
);
We once again update our `ReactDOM.render` method to use our new component.
ReactDOM.render(
    <div className="commmentBox">
        <CommentList comments={ comments } />
        <CommentForm />
    </div>,
    document.querySelector('#content')
);
Now we have a form that doesn’t actually do anything. So how do we solve that? Think about how we expect our users to interact with the comment form. We will expect them to enter in their name in the author field, the text of the comment in the text field, and to hit the submit button when they have completed their comment. We can handle the author and text field changes with an `onChange` handler and we can deal with the submit with and `onSubmit` handler. Let’s start with the latter to illustrate.
const CommentForm = (props) => (
  <form className="commentForm"
    onSubmit={(e) => {
      e.preventDefault();
      comments = [
       ...comments,
       {
         author: e.target.elements.author.value,
         text: e.target.elements.text.value
       }
      ]);
    }}
  >
    <input type="text" name="author" placeholder="Your name" />
    <input type="text" name="text" placeholder="Say something..." />
    <button>Post</button>
  </form>
);
This works for the comment form, or rather, it updates the comments array, it doesn’t update the view to display it yet. We’ll get to that in a bit. There are a number of other problems with it too, though. First, we are forcing our otherwise dumb component to be aware of how to handle a submit, rather than just that a submit may occur. Second, our component is directly accessing the DOM, which is something that React studiously wants us to avoid.
Instead, it would be better if we maintained the value of the fields in the state of the application, and passed what the CommentForm should do down to it. This will also help solve our UI update problem as well.

The State of Things

Now, React is perfectly able to handle maintaining state on it’s own, but those are not the opinions that are going to be expressed here, and I don’t want to go any farther down a road we will eventually abandon.
Instead, we are going to introduce the other half of this architecture. Redux.
Redux is a state manager, but it’s more than that. The Redux readme does a pretty good job on the elevator pitch.
Redux is a predictable state container for JavaScript apps.
It helps you write applications that behave consistently, run in different environments (client, server, and native), and are easy to test. On top of that, it provides a great developer experience, such as live code editing combined with a time traveling debugger
You can use Redux together with React, or with any other view library.
It is tiny (2kB) and has no dependencies.
Not bad eh?
There are a few principles upon which Redux rests.
  1. The state of your app is represented by a single, immutable, plain JavaScript object called a “state tree”.
  2. The state tree is read only. Changes to the state tree are not performed directly, but are initiated by dispatching an “action”, which is a plain JavaScript object identifying what part of the state should change.
  3. Actual changes to the state tree are described through pure functions that take the current state and an action, and which return a new state. These pure functions are called “reducers.”
One of the interesting effects of Redux is that your actions end up defining explicitly what all the things are that could possibly affect your application. It’s self documenting! This often leads to actions being the first things that are defined for an application, as it spells out how the rest of the application is to be built.
Defining all the actions for an application takes some up front thought. The more complete you are in the beginning, the easier the rest of the application building will be. Take some time and think of all the things a user can do that should affect the state of your application. As a matter of convention, action “types” are defined as constants who’s values are either all uppercase strings or Symbols. Let’s use Strings.
We will need to update the comment list when a new one is added. We also need to keep track of the author and text input fields, so we’ll need to know when those update as well.
const ADD_COMMENT   = 'ADD_COMMENT'
const AUTHOR_CHANGE = 'AUTHOR_CHANGE'
const TEXT_CHANGE   = 'TEXT_CHANGE'
That should do it for now. These are the action “types.” As I mentioned before, the actions are just plain JavaScript objects. The only constraint is that they must have a “type” property. Any other information that is needed to update the state appropriately can then be added. For consistency’s sake, it is recommended that action creators are used to make sure actions of the same type get the same information. Action creators are just functions that return an action object. We’ll make an action creator for each action type.
const addComment = (comment={author: '', text: ''}) => ({
    type: ADD_COMMENT,
    comment
});
const authorChange = (author='') => ({
    type: AUTHOR_CHANGE,
    author
});
const textChange = (text='') => ({
    type: TEXT_CHANGE,
    text
});
Again, no magic here. Just a few ordinary functions that return ordinary JavaScript objects.
Now, let’s define how these actions affect the state tree. We do this with reducers. Reducers are functions that accept the current state and an action and returns a new state. Our application at this point is pretty simple. We will only use one reducer and switch on the type of the action that comes in. We will also use ES6 default parameters to make sure all the properties on the state object passed in exist and are of the right type. This also acts as a handy reference to document the shape of the state we expect.
const commentsReducer = (state={
    items:[],
    author:'',
    text: ''
}, action) => {
    switch (action.type) {
        case ADD_COMMENT:
        return {
            ...state,
            items: [...state.items, {id: Math.random(), ...action.comment}]
        };
        case AUTHOR_CHANGE:
        return {
            ...state,
            author: action.author
        };
        case TEXT_CHANGE:
        return {
            ...state,
            text: action.text
        };
        default:
        return state;
    }
};
You can see, for every action type, we return a new state — this is very important. If a type we are not expecting is found, the default case matches and the unaltered state is returned. This coupled with the default parameter we defined for the state argument means when ever the reducer is called, it will return an object of the shape we expect, and it will do so without mutating any previous state objects. The implications of this are profound.
To initiate a state tree we create something called a “store.” A Redux store is simply a wrapper around the state tree and which provides us a couple methods for interacting with it. To create the store we use Redux’s `createStore` method passing it our reducer.
const { createStore } = Redux;
const store = createStore(commentsReducer);
We can see the state of the store at any time by calling the store’s `getState` method.
const { createStore } = Redux;
const store = createStore(commentsReducer);
const { getState } = store;
console.log(getState());
If you look at the object in the console, you’ll notice that the items array — the array we’re going to hold our comments in — is empty. This is because we haven’t dispatched any actions. Remember, the only way to change the state in a Redux store is through actions.
`dispatch` is a method on a Redux store. Let’s dispatch an `ADD_COMMENT` action for each comment we have in our comments array using our `addComment` action creator.
const { createStore } = Redux;
const store = createStore(commentsReducer);
const { getState, dispatch } = store;
comments.map(comment => dispatch( addComment(comment) ));
console.log(getState());
Now our console shows comments in our items array.

Tying it all together

At this point, we have two different and disconnected things going on. We’ve got React rendering UI to the screen, and we’ve got Redux managing state, but the two aren’t talking to each other. What we need to do is pass the state tree down through our component tree. We could do this by passing it down directly as props on each component in the tree…
ReactDOM.render(
  <div className="commmentBox">
    <CommentList comments={store.getState().items} />
    <CommentForm author={store.getState().author}
                 text={store.getState().text} 
    />
  </div>,
  document.querySelector('#content')
);
…this works, but it’s not terribly efficient. Instead. We can introduce a new type of component called a higher-order component. In React, a component that wraps another component and extends or enhances it’s functionality is called a higher-order component.
We can use the concept of a higher-order component in order to provide the Redux state to our React components. In fact, this is so common, the Redux folks create a little library called react-redux for tying Redux and React together that offers just such a component. It is called the Provider component and it works thusly.
const { Provider} = ReactRedux;
ReactDOM.render(
  <Provider store={store}>
    <div className="commmentBox">
      <CommentList comments={ comments } />
      <CommentForm />
    </div>
  </Provider>,  document.querySelector('#content')
);
The Provider component provides the current state to every component in the component tree as part of the component’s `context`. A component’s `context` is available as the second argument passed to our component.
Let’s rework our `CommentForm` component from earlier to use our Redux store. There are a few things we need to do to make this work. First, we will destructure our context parameter to extract the values we are concerned with, namely `author` and `text.`
const CommentForm = (props, { author, text }) => (
  <form className="commentForm"
        onSubmit={ (e) => {
          e.preventDefault();
          comments = comments.concat({
            author: e.target.elements.author,
            text: e.target.elements.text
          });
        }}
  >
    <input type="text" name="author" placeholder="Your name" />
    <input type="text" name="text" placeholder="Say something..." />
    <button>Post</button>
  </form>
);
Next, we need to dispatch the appropriate actions to ensure the store is kept in the proper state.
const CommentForm = (props, { author, text }) => (
  <form className="commentForm"
        onSubmit={ (e) => {
          e.preventDefault();
          dispatch(addComment({ author, text }));
        }}
  >
    <input type="text"
           name="author"
           placeholder="Your name"
           onChange={(e) => dispatch(authorChange(e.target.value))}  
    />
    <input type="text"
           name="text"
           placeholder="Say something..."
           onChange={(e) => dispatch(textChange(e.target.value))}
    />
    <button>Post</button>
  </form>
);
Now, this solution is quite a bit more elegant, but there’s still a substantial problem. We are not maximizing reuse. This component has to know too much app specific knowledge. It has to know our architecture is Redux, where app state is obtained through the context object and not props. It has to have access to our store’s `dispatch` method. It also has to know about the `addComment`, `authorChange` and `textChange` actions. Additionally, we would need to change our CommentList component in a similar way in order to get the list of comments from the context rather than props.
Instead, let’s try to move our app logic out of this component, and into a component which is intentionally app specific. We can consolidate as much app logic here as we can and keep the rest of our components truly reusable. This “smart” component will be responsible for subscribing to our store, dispatching actions as appropriate, and defining and passing down behavior to our pure components.
Right now our root component is just a `div`. We’re going to change that to a new smart component we’ll call CommentBox. Because of how a smart component is used, we will need to create it in a bit of a different way. We’re going to use a different way to define a React component we haven’t talked about yet, by using a class.
const { Component } = React
class CommentBox extends Component {
  render() {
    const { items, author, text } = this.context.store.getState()
    return (
      <div className="commentBox">
        <h1>Comments</h1>
        <CommentList comments={items} />
        <CommentForm author={author} text={text} />
      </div>
    )
  }
}
You can see that we are using a plain boring JavaScript class which extends a Component class React provides. The only method on the class that is always required is a `render` method. In our render method we get the current state from our store (which in turn we retrieved from `this.context`) and use that state to populate the props that go to our dumb components.
Apart from render, there are other members on the class that are React specific and may be required under certain conditions. One of those properties is ‘contextTypes.’ The contextTypes static property is required whenever the `context` object is going to be used by a component. It maps properties on your context object to a type validator. React does this for performance reasons as well as security. React provides a number of built in validators. If you like, you can peruse all the built in validators here at your leisure. For now, it’s sufficient to declare that our context object will have a property called ‘store’ which will be an object.
const { Component, PropTypes } = React;
class CommentBox extends Component {
  static contextTypes: {
    store: PropTypes.object
  }
  render() {
    const { items, author, text } = this.context.store.getState()
    return (
      <div className="commentBox">
        <h1>Comments</h1>
        <CommentList comments={ items } />
        <CommentForm author={ author } text={ text } />
      </div>
    );
  }
}
The last piece missing for this component is subscribing to our Redux store. We don’t want to do this until after our component has been rendered. Since we want our components to be good citizens of our application, we also want them to clean up after themselves, so we will want to unsubscribe from our store just before our component is removed or destroyed. There are other key moments in the lifecycle of a component, and for this reason, React components have access to lifecycle methods. The ones we are concerned with today are the `componentDidMount` and `componentWillUnmount` lifecycle methods — which give us the opportunity to subscribe and unsubscribe to the React store.
const { Component, PropTypes } = React;
class CommentBox extends Component {
  static contextTypes: {
    store: PropTypes.object
  }
  componentDidMount() {
    const { store } = this.context;
    this.unsubscribe = store.subscribe(() => this.forceUpdate())
  },
  componentWillUnmount() {
    this.unsubscribe(); 
  },
  render() {
    const { items, author, text } = this.context.store.getState()
    return (
      <div className="commentBox">
        <h1>Comments</h1>
        <CommentList comments={ items } />
        <CommentForm author={ author } text={ text } />
      </div>
    )
  }
}
Here, when the component “mounts” or is rendered to the view, we subscribe to changes to the store. This means when ever the store changes, our handler is called forcing the React component (and all it’s children) to update. This may sound like we could be making our component do unnecessary rendering to the view, but all this does is trigger React’s virtualDOM render which is super fast. The new virtualDOM is still intelligently diffed with the current virtualDOM and only the minimal updates are made to the view.
Now let’s go back to our CommentForm component and move our app logic from there…
const CommentForm = (props) => (
  <form className="commentForm"
    onSubmit={ (e) => {
      e.preventDefault();
      props.onCommentSubmit();
    }}
  >
    <input type="text"
           name="author"
           placeholder="Your name"
           value={ props.author }
           onChange={(e) => props.onAuthorChange(e.target.value)}
    />
    <input type="text"
           name="text"
           placeholder="Say something..."
           value={ props.text }
           onChange={(e) => props.onTextChange(e.target.value)}
    />
    <button>Post</button>
  </form>
)
…to the CommentBox component…
const { Component, PropTypes } = React;
class CommentBox extends Component {
  contextTypes: {
    store: PropTypes.object
  }
  
  componentDidMount() {
    const { store } = this.context
    this.unsubscribe = store.subscribe(() => this.forceUpdate())
  }
  
  componentWillUnmount() {
    this.unsubscribe()
  }
    
  render() {
    const { items, author, text } = this.context.store.getState()
    return (
      <div className="commentBox">
        <h1>Comments</h1>
        <CommentList comments={items} />
        <CommentForm
          author={author}
          text={text}
          onCommentSubmit={()=>dispatch(addComment({author, text}))}
          onAuthorChange={(author)=>dispatch(authorChange(author))}
          onTextChange={(text) => dispatch(textChange(text))}
        />
      </div>
    )
  }
}
With our successful dumbing down of the CommentForm and smartening up of the CommentBox component, we can update our `ReactDOM.render` method to use our new, super intelligent CommentBox component.
const { Provider} = ReactRedux;
ReactDOM.render(
    <Provider store={ store }>
        <CommentBox />
    </Provider>,
    document.querySelector('#content')
);
We now have a fully functional Chat app that reacts to user interactions and keeps it’s view in sync with it’s own application state.
There’s still several more to cover to be production ready such as making network calls, network initiated actions vs user initiated actions, Redux middleware, “Thunks,” recommended file structure, etc. We will cover these things in subsequent tutorials. Until then happy reacting.
end.
React + Redux Tutorial React + Redux Tutorial Reviewed by Unknown on November 30, 2017 Rating: 5

No comments:

Powered by Blogger.