Component based web UI with ReactJS

Reading Time: 10 minutes

Component based Web UI with ReactJS

Recently, I started a web frontend project using ReactJS. I am working with AngularJS on an enterprise application, but I thought for this relatively small project it would be a total overbloat using Angular. As I started to read about React, my curiosity grew. Most critics were positive, especially from those who already worked with React, and so I decided to give it a try. Now, after some days of seriously working with ReactJS, I would like to share my experiences.

Introduction

If you have never heard of React before, I'll you give a short introduction, otherwise you can skip this paragraph.

React was created by Facebook and opened for community in 2013. Beside Facebook and Instagram there are a a lot of sites in the wild using React. It is not one of these fullstack MV*-frameworks, like Backbone, or Angular. It is a library for building component based views. In my opinion, this an advantage as React does not depend on any other library, and therefore offers very good interoperability with other libraries. React is known for its impressive rendering performance, which is possible through the concept of the Virtual DOM, a lightweight in-memory DOM representation. React updates the browser's DOM only by the differences between one state and another. This is one of the key features. Furthermore, React breaks with the convention of separating view and logic. Components are self-containing, mixing logic with representation. This makes sense, as the reality shows that in HTML-JS-world most of the logic is strongly coupled to the view.

In a summary, the following characteristics apply for React. It is

  • battle-tested
  • relatively tiny
    • the production version with addons is about 130KiB
  • library agnostic
    • no dependencies
  • easy to learn
    • concise and tiny API
  • efficient
    • Virtual DOM concept leads to impressive rendering performance
  • isomorphic
    • can be used on client, but also on server side

So, let's take a look in some more interesting details, which makes React different.

JSX

First, of all, some code to get in the right mood. The following example is a Login component.

var LoginForm = React.createClass({

    credentials : {},

    // React API: Called to the very beginning before component is rendered or mounted
    getInitialState: function () {
        return {login : ''};
    },

    // React API: Called when component was mounted to real DOM the first time
    componentDidMount: function(){
        document.getElementById('username').focus();
    },

    update : function(event){
        credentials[event.target.id] =  event.target.value ;
        this.setState( {login : ''} );
    },

    login: function (event) {
        var authService = new AuthService();
        authService.login(credentials.username, credentials.password)
          .then(function (token) {
            $auth.setAuthToken(token);
            $route.gotoStartPage();
        }).catch(function () {
            this.setState( {login : "Login failed"} );
        }.bind(this));
    },

    // React API: Called everytime when an update is requested, i.e. by setState() or forceUpdate()
    render: function () {
        return (
            <form id="login">
                <small>&nbsp;{this.state.login}</small>
                <InputField id="username" type="text" onChange={this.update}></InputField>
                <br/>
                <InputField id="password" type="password" onChange={this.update}></InputField>
                <br/>
                <div className="wrapper">
                    <span className="group-btn">
                        <a href="#" className="btn btn-primary btn-md" onClick={this.login}>Entrar&nbsp;
                            <i className="glyphicon glyphicon-off"></i>
                        </a>
                    </span>
                </div>
            </form>
        );
    }
});

So, a lot of things here, but the weirdest thing is the XML mixed with Javascript. At first glance, this smells like hell. The truth is, that this not even necessary, because this syntactic sugar is for better readability. This mixed code is called JSX (Javascript with XML) and gets transpiled by React's JSX transforming tool. The transformed code inside the render function will look like this:

render: function () {
    return (
        React.createElement("form", {id: "login"}, 
            React.createElement("small", null, " ", this.state.login), 
            React.createElement(InputField, {id: "username", type: "text", placeholder: "usuário", onChange: this.update}), 
            React.createElement("br", null), 
            React.createElement(InputField, {id: "password", type: "password", placeholder: "senha", onChange: this.update}), 
            React.createElement("br", null), 
            React.createElement("div", {className: "wrapper"}, 
                React.createElement("span", {className: "group-btn"}, 
                    React.createElement("a", {href: "#", className: "btn btn-primary btn-md", onClick: this.login}, "Entrar ", 
                        React.createElement("i", {className: "glyphicon glyphicon-off"})
                    )
                )
            )
        )
    );
}

JSX emulates HTML with some minor exceptions. For example, the attribute class becomes className, because class is a reserved keyword in Javascript. So, you can write your structure with JSX, and mix it up with the dynamic parts embraced by curly brackets. The code shows, that is possible to embed other components. In the above example we embed the custom InputField component, which is created elsewhere, and reused by the Login component. This is a big deal, because this way we can build our UI like Lego®.

Stateful Components

The central concept of React is the renderization of changed parts only. The render process is triggered, when the components state changed. Changing a state is done using the setState function.

In the above example, the state is a fail message that appears when login was not successful. A state must not necessarily be visualized, but a state always should be used when component needs to be re-rendered. Try to keep the number of states as less as possible and think twice if a prop is more appropriate.

var LoginForm = React.createClass({

    credentials : {},
    // if we use states, we need to initialize them.
    getInitialState: function () {
        return {login : ''};
    },

    // with this method we clear eventual messages
    update : function(event){
        credentials[event.target.id] =  event.target.value ;
        this.setState( {login : ''} );
    },

    login: function (event) {
        var authService = new AuthService();
        authService.login(credentials.username, credentials.password)
          .then(function (token) {
           //...
        }).catch(function () {
            // stay on login page an re-render the page --> calls render()
            this.setState( {login : "Login failed"} );
        }.bind(this));
    },

    // from React API: called when updating the screen, e.g. through state changes
    render: function () {
        return (
         <form id="login">
              <small>&nbsp;{this.state.login}</small>
              <InputField id="username" type="text" onChange={this.update}/>
          // ... and so on
        ); 
    } 
});

 

Important: You need to call setState to trigger the render process. Changing the state member directly can be done either, but then no update of the component occurs. In the latter case you might force an update using forceUpdate()

Properties and Event Delegation

Components can receive their own attributes, and in conjunction with the synthetic event system of React it is possible to pass captured events to parent components. To pass arguments, the React API offers the props member, available in each component. For example, the InputField component has three properties: id, type and onChange.

<InputField id="username" type="text" onChange={this.update}></InputField>

 

Within the InputField component looks like this:

var InputField = React.createClass({render: function () {
        return (
            <input id={this.props.id}
                   type={this.props.type} 
                   className="form-control input-sm chat-input" 
                   onChange={this.props.onChange} />
        );
    }
});

 

And the delegation of the onChange event to the LoginForm component works like this:

    // called by delegated onChange event from InputField
    update : function(event){
        credentials[event.target.id] =  event.target.value ;
        this.setState( {login : ''} );
    },

    // React API: Called everytime when an update is requested, i.e. by setState() or forceUpdate()
    render: function () {
        return (
            <form id="login">
                <small>&nbsp;{this.state.login}</small>
                <InputField id="username" type="text" onChange={this.update}/>
                // ...
        )
    }

You can pass any type as property. In our example, the InputField applies a function to the onChange event, which then delegates the callback to the parent component (here: LoginForm).

This kind of communication works only, if your component has a parent component. The good thing of React is, that you are not forced to build up your page entirely of components. To make isolated components (not within a hierarchy) communicate with each other you may use a message system, like EventEmitter. I will cover this topic in the near future in another article.

Component Lifecycle

Each component owns specific callback functions which are called by React during a components life. Most of the time you will use getInitialState(), componentDidMount(), componentDidUpdate(). The names are self-explaining. Nevertheless, it is important to know an essential fact. If you plan to manipulate a node, you must know whether that node already exists in the browsers DOM (mounted), or not (unmounted). This is a typical situation when you use jquery (and it plugins) or the native query selector API (for some reason) in conjunction with React.

The component is mounted, i.e. the real DOM is only accessible, within one of the following lifecycle methods:

  • componentWillUnmount
  • componentDidMount
  • componentDidUpdate

All other callbacks work on an unmounted component, which you need to keep in mind if you pretend to work with the real DOM.

 

Accessing DOM Nodes

So, you may ask yourself how to access a real DOM Node from within React. Normally, you should avoid it and try to learn how to manipulate most things within the virtual DOM using React. But in a real world application there are situations when you cannot avoid it, for example you need to manipulate an element (e.g. toggle css class) on a certain event, that is not linked to the triggering element. In this case, the refs attribute is your friend.

Hint: You can use jQuery or querySelector to access the node. But it is cleaner to use the refs attribute, as you do not need to pay attention on ids. As ids must be unique, it becomes cumbersome if you plan to use many instances of the same component in your view.

The following example is from a real world filter component. On button click, the filter data will be collected from input elements using the refs attribute and broadcasted. The trick is, that React has a special element attribute called ref (similar to HTML id attribute), which can be used to identify/reference a React element. The method getDOMNode() returns the eventually mounted DOM Node. Once again: The node must be mounted when getDOMNode() is called. In our example this is guaranteed implicitely, because the method searchByFilter() is called when the user has clicked the button.

Update: getDOMNode is deprecated and has been replaced with React.findDOMNode().

// This method is called on click by the button element - hence, it is really mounted
searchByFilter: function(){
   var filter = {
      // here we access the element using ref
      dataIni : this.refs.dataIni.getDOMNode().value,
      dataFim : this.refs.dataFim.getDOMNode().value
   };
   $event.emitEvent('filter-changed', [filter]);
},


render: function () {

   return (
      <div className="panel panel-default">
         <div className="panel-body">
            <div className="input-daterange input-group" id="datepicker">
               <input ref="dataIni" type="text" className="input-sm form-control" name="start" placeholder="Data inicial"/>
               <span className="input-group-addon">até</span>
               <input ref="dataFim" type="text" className="input-sm form-control" name="end"  placeholder="Data final"/>
            </div>

            <hr/>

            <div className="row ">
               <div className="form-group col-xs-12 col-sm-12">
                  <button type="button" className="btn btn-default pull-right" onClick={this.searchByFilter}>
                     <span className="glyphicon glyphicon-search" aria-hidden="true"></span>
                     <span>&nbsp;</span>
                     Pesquisar</button>
               </div>
            </div>
         </div>
      </div>
   );
}

So far, so good!

This was a brief overview with some code insights in ReactJS. Since I started using React I passed very interesting and most times positively surprising moments. I needed some days to adopt the concept of component oriented design, but it was all worth it. Designing and reusing components is fun, but it is more fun to refactor components. Because of centralized points of refactoring the view evolves much easier and more seamlessly. For sure, I will use React in more projects.

From the current project I extracted a project bootstrapper. So, if you want do kick off now, feel free!

Facebooktwittergoogle_plusredditpinterestlinkedin

4 comments

    1. Thanks. Although, it is a bit outdated, e.g. about refs and the createClass-Syntax, it still covers the basics of ReactJS. Btw: Don't use the bootstrapper I linked...I recommend to go with Create React App, which is a really awesome tool!

Leave a Reply

Your email address will not be published. Required fields are marked *