Capabilities of React.js
What is React?
React is not a frontend framework but rather a library offering a new approach to build view. It creates a virtual DOM to track changes when the application state is updated and uses the virtual DOM to update only those parts of the page which have been changed. Since all DOM manipulations occur simultaneously in an efficient way, it makes UI lightning fast.
Why should I use React?
Since React is just a view library it is easy to apply it to the existing code base and decide if it fits or not.
It uses the declarative approach to describe components, and the components API is pretty small, so the learning curve is not steep. It is also handy for teamwork because components have a predefined structure.
React is isomorphic. It is possible to use React everywhere where you can execute JavaScript. If you need to improve the initial page loading and do SEO, you can render the page on the server side with a help of Node.js or Java 8 Nashorn. Or you can even use it on TV and game consoles like Netflix does.
The library is supported by Facebook and has already been used by Facebook in production on different projects.
The community support is also growing rapidly and a number of companies (Netflix, AirBnB, SoundCloud, Yahoo!, Codecademy to name just a few) choose React for production.
React core concepts
Component
The basic unit of a React application is a component. A React component is a mix of markup and JavaScript which is rendered into an HTML fragment.
Components are nested into each other in the same way as HTML tags.
A simple react component looks like this:
var MyComponent = React.createClass({ render: function () { return <div>This component is rendered as a block with a text</div>; } });
The same component parsed to JavaScript:
var MyComponent = React.createClass({displayName: "MyComponent", render: function () { return React.createElement("div", null, "This component is rendered as a block with a text"); } });
Mixing markup and JavaScript in a single place may seem a bad idea, but this is what any JavaScript UI library does. Since there are still tasks that cannot be solved with only HTML and CSS, sometimes a JavaScript solution turns out to be more efficient. So it is acceptable to use JavaScript to change the view as long as you have your application logic handled separately. From this point of view, React components are just an extension of the existing HTML elements.
Imagine you have a <Header> component which is supposed to render a title in a nice way, and you want it to stick to the top during scrolling. It is easy to extend HTML with a new 'element' to encapsulate this 'stick to the top' logic.
<StickyTopComponent> <Header>Company Name</Header> </StickyTopComponent>
<StickyTopComponent> is responsible for displaying its child elements always on top.
As long as the application logic lives in a separate place, you have to be able to pass data to the view layer to display it. React components do this in HTML in a similar way. All you need to do is to pass values through attributes.
<Tooltip position="left">Please, fill this field</Tooltip>
Now <Tooltip> knows that it should appear to the left of another element and can draw a pointer on the right side.
JSX
Essentially React components are simple objects, but thanks to JSX they can be presented much like HTML tags. JSX is XML-looking syntactic sugar like the syntax inside JavaScript code.
Moreover, JSX enables you to pass object as an attribute value:
var List= React.createClass({ render: function () { return ( <ul> {this.props.values.map(function (val) { return <li>{val}</li>; })} </ul> ); } }); // ... <List values={['milk','sugar','meat']}/>
You can even pass a function to a component which is widely used for event listeners and communication between parent and child components.
Eventually, this is parsed to JavaScript and you just pass an object to the function.
var List= React.createClass({displayName: "List", render: function () { return ( React.createElement("ul", null, this.props.values.map(function (val) { return React.createElement("li", null, val); }) ) ); } }); // ... React.createElement(List, {values: ['milk','sugar','meat']})
Since attribute names are converted to object fields, JSX expects the usage of 'className' instead of 'class' and 'htmlFor' instead of 'for' attributes.
It is handy to use JSX because you see where representation is and where other calculations are. It is also easier for designers to change small pieces of UI without digging into JavaScript logic. However, JSX syntax is optional, and you could just as well write your components in JavaScript.
State and Props
React components are called state-full because the output of the component render() function is always determined by the data a component contains. There are two places where new data are passed to a component: props and state.
var InputCounter = React.createClass({ getInitialState: function () { return {count: 0}; }, render: function () { return <div>{this.props.label} : <input type='text' onInput={this.countSymbols}/> {this.state.count}</div>; }, countSymbols: function (e) { this.setState({count: e.target.value.length}); } }); // ... <InputCounter label='How many symbols in'/>
A component is instantiated with 'props' and 'state' fields. A properties object is a map of attributes and their values. Props are used to pass data from the parent to child components. A child component must not change the properties it has received from the parent component. The internal condition of a component is represented by the state object and its data can be changed by the component itself.
The ability to encapsulate a component's internal state simplifies the separation of the application logic from the view layer. A component should only change state itself when it is related to its representation and have to pass manipulations related to the application data.
Reusable components
Components that do not contain application logic can be reused in different ways. React encourages writing reusable components and provides an API to unify the components structure.
Writing a component you can define which properties it can have and what the required type for each property is. You can also define the component's default properties and default state. Having this done in a declarative way is very useful to work with the third-party components.
Moreover, properties can be passed through components making it easy to create a thin wrapper around the nested components.
Virtual DOM
React keeps in memory a simple object tree of all components which is called the virtual DOM. Whenever the application state changes, and it is required to update the UI, the virtual DOM is used to find the minimal set of operations to bring the view from the previous condition to the new one.
Let’s take a look at the component below:
var Example1Component = React.createClass({ render: function () { if (this.props.isMain) { return <p className="main"><strong>Title</strong></p>; } else { return <p className="secondary">Title</p>; } } });
If the component was used as <Example1Component isMain={true}/> and it is changed to be <Example1Component isMain={false}/>, then React will calculate that it is only needed to change the class on <p> element and replace <strong> with a text node.
It is a simple example, but it provides a basic understanding of React diff-algorithm.
In a more complex application, several levels of nested components will create an object tree. In this case when setState() is called for the top most component, it recalculates all nodes below.
In order to optimize re-rendering React starts from the lowest nodes and compares only components on the same level of the tree.
Child components are compared by classes. So if a component had <Link/> child, but now it should render <Button/>, it will not compare these different components but just replace the older one.
React also optimizes lists comparison by assigning keys to list components. If components in the list have keys, it is much easier to find which component has been changed, added or removed.
All these optimizations are possible because React provides an abstraction from direct DOM manipulation describing components as objects and composing them into a virtual DOM.
Event handling
React creates its own event system which is fully compatible with W3C specification. All browser’native events are wrapped by instances of SyntheticEvent. It provides a cross-browser interface to a native event. That means you do not need to worry about inconsistent event names and fields. Moreover, React event system is implemented through event delegation and also has a pool of event objects to reduce memory overhead.
How to build a sophisticated app using React
The main question you will face when you decide to use React is: if React is just a view layer, how can I build an enterprise application with a sophisticated client side logic using it?
Building applications with React is simple because you can integrate it with any architecture and framework you know using it just like a template engine. Components receive state and props, calculate what UI should look like and then render or update the DOM. Components also define event listeners to handle UI events. Events are processed and the state is updated again. That is pretty much everything that you need to know to start using React.
Reusable components
When you write a reusable component you want it to be possible to use it in different places in your application or to share it with other developers. Therefore, a component should be as generic as possible. It should work with any data model passed to the state and the logic inside the component should only work for UI calculations. It is a good practice to wrap a general component in a container component that might convert an application specific data model to the state model of the wrapped component.
Flux
In order for the application to know about user interactions and data changes, a component must never change the data itself. Instead, component fires events that application should process some action and change some data. When data are updated, they are passed back to the component by updating its state. Components never change the state of other components directly but fire events and then the application defines the components the state of which should be changed. This means that data flow in a single direction from components to an application and back to the components.
An application architecture that describes the unidirectional data flow is called Flux. According to Flux, when a component receives an event it sends an action object with some payload. The application has a single Dispatcher that handles all actions and distributes them to registered stores. The stores contain the data from a specific application domain and the logic to operate with these data. A component listens to changes from the store and updates its state.
Flux architecture is straight and aimed at simplifying the data flow in sophisticated applications, but there are possible pitfalls you should be aware of while using Flux.
Imagine you have two instances of the same component. That means that they trigger the same actions. How to distinguish which component triggers an action? One solution is to create a specific wrapper around each component. Wrappers handle events from the embedded component and fire own actions. Using this approach you will have to write a lot of code for wrappers and actions. Another solution is to have an ID for each component and send it with actions payload. In this case your stores should know IDs of the components they want to communicate with. Therefore, the store should take an ID from a component when it is registered and check that the actions target ID is appropriate.
Handling AJAX
It is up to you how to organize client-server communications in your application. What’s more, even Flux pattern does not describe how to work with asynchronous code. There are two places where you could handle AJAX responses. The first is right inside the store where you make a request. It is a decent place if you do not have other stores which have to update data based on the same resource. Another option is to fire an action when a response is received and put its data to the action payload. The dispatcher then distributes the action to stores.
In the same way you could handle another asynchronous operation. If an operation updates data of its store only, it can be handled right in the place. If it has to change some common data, it should trigger an action.
Data model
Stores are environments where you work with your application data. There are no rules regarding what they should look like. It is perfectly acceptable to use Backbone.js or any other library to create data models. However, the most powerful solution is to use immutable data structures. An immutable object perfectly fits the React rendering process because it is easy to check if an object has been changed or not. An immutable object cannot be changed. When you are changing a field of an immutable object, you get a link to a new object. It is almost the same: all its fields, except one, point to them same values. To check if an object has been changed, you need to check if the link to an object and the link you have on hand are the same. You do not have to iterate the whole object tree and compare each nested field. This is why it is extremely useful when React considers re-rendering a component.
Conclusion
Applications built on React use new principles that have not been used in web development before. It is a perfect tool to make high-performance presentation layer for your application. And it can be even faster if you take the advantage of immutable data structures. But with the new approaches to the rendering process, it also gently nudges you to rethink the whole architecture. As always, you should put “reusable” at the core of the code making your components generic and separate view from the logic. There are plenty of Flux implementations you can choose from, including the brand new framework Relay. But beyond any doubt React is a library worth to take a look at.