Thinking in React for jQuery Programmers
React allows developers to create UI components easily and quickly utilizing a declarative, reactive programming model without sacrificing performance by abstracting the DOM away from the programmer. If you're used to writing web applications using just jQuery and manual DOM manipulation, here are five tips that may help you think about writing components "the React way."
Tip 1: Your UI should be a function of your data
In a typical jQuery application, the business logic and UI manipulation code are often intermingled, leading to difficult-to-understand code and hard-to-fix bugs. In React applications, you should think about your UI through a data-first design; questions like "What data is required for this piece of the UI?" and "How can I transform that data into DOM components?" is the key.
Given the same data, a React component should always return the same output, making React components easy to reason about and test. Because of this, when you ask yourself, "How do I update my UI to be a certain way?" what you should actually be asking is, "What data do I need to change to cause my data-to-DOM transformation step to output the right thing?"
Tip 2: Don't touch that DOM!
This is closely related to the above point. React abstracts the real DOM away from you by utilizing a "virtual DOM," which is a set of data structures that represents what you want the real DOM to look like. For this reason, you shouldn't manipulate the DOM yourself; instead, use React's declarative programming model to tell it what you want to render, and let React take care of manipulating the DOM.
For a simple example, in a jQuery application, you might keep track of a boolean state, and add or remove a class based on it:
$myBtn = $("<button>");
$myBtn.addClass("button");
if (toggled) {
$myBtn.addClass("toggled");
}
$myBtn.appendTo(...);
// later, when `toggled` might have changed...
$myBtn.toggleClass("toggled", toggled);
Using React, you simply keep track of the data in the component's internal state and tell React what to render based on the value:
render: function() {
var classes = "myButton";
if (this.state.toggled) classes += " toggled";
return <button className={classes}>...</button>;
}
Since React is a reactive UI library, whenever the toggled
state in the component changes, React will recalculate what the DOM should look like (based on the output of render
), and will then go modify the DOM if and only if any changes actually needs to be applied. React also batches DOM updates, and attempts to find a minimal set of DOM operations, for further performance. If you manually mess with the DOM, you could easily mess with React's ability to apply these changes correctly.
Of course, sometimes you just can't implement a certain feature without manipulating the DOM manually; in these cases, React provides nice lifecycle hooks that you can utilize to make sure you're able to do so without breaking React's rendering cycle—but it shouldn't be the default way to approach building components!
Tip 3: Invert your data flow
It's easy to be tempted to create a huge tree of React components with each one managing a little piece of application state inside it. However, in this case, it begins to get difficult to determine which piece of app state is managed by which component.
A better approach is to centralize data ownership and management into as few places as possible (especially toward the top of your component hierarchy) and pass this data into lower-level stateless components via properties. If those components also need to modify the data, they should be passed references to functions they can call that will update the data at the top level. This has the added benefit of making the stateless components much simpler, and thus easier to understand, debug, and test.
In larger applications, patterns like flux and other data-centralization techniques can help further manage keeping your data and change operations in one place.
Tip 4: Embrace composition!
It's often easiest to get started by writing a huge React component that does everything you need it to, just to get an idea working. Once you've done that, it's a good opportunity to take a step back and think about breaking the functionality into smaller sub-components. (For bonus points, start thinking about this concept before you start writing your first component! You may be surprised how easy it is to identify components for your application just by looking at a mock.)
Think about isolated pieces of your UI that deal with one specific piece of data or presentation logic, or that is repeated more than once with slight variations. These are often good candidates for becoming their own components. These new sub-components can also be broken down into even smaller components when it makes sense to do so.
For example, take a look at the following component fragment:
render: function() {
return (
<div className="container">
<div className="login-logout">
<img src={getAvatarUrl(this.props.user)} />
Logged in as {this.props.user.username}.
<a href='#' onClick={this.logout}>Logout</a>
</div>
<div>
<h1>Your posts:</h1>
{this.props.posts.map(this.renderPost)}
</div>
<div>
<h2>Comments:</h2>
{this.props.comments.map(this.renderComment)}
</div>
</div>
);
}
You could easily break this into a component that looks more like:
render: function() {
return (
<div className="container">
<LoginLogoutBar user={this.props.user} logout={this.logout} />
<PostsList title="Your posts:" posts={this.props.posts} />
<CommentsList title="Comments:" comments={this.props.comments} />
</div>
);
}
This can be broken down even further; for example, LoginLogoutBar
could make use of a component called UserAvatar
that renders the correct avatar for a given user.
Having a larger number of smaller, more reusable components is generally preferable to a small number of giant, complex ones. It makes your app more flexible, easier to test, and easier to reason about (due to black-boxing of certain concerns).
Tip 5: Beware mutable data
One of the reasons React has such good performance characteristics is because it knows when data has changed, and thus can figure out what the minimal subset of your UI is that it needs to render. However, since React is a JavaScript framework, this isn't always an easy task.
React relegates data changes to a calls to setState
; if you don't call setState
to update your state, your component won't rerender. For this reason, it's useful to be wary of mutable data. It's easy to pass an object to some function and not realize that it mutates that object directly, which can cause unexpected behavior. Furthermore, React batches UI updates asynchronously when data changes, so manually mutating data can actually cause some operations to be lost!
React provides immutability helpers to assist with operating on deep sub-trees and sub-arrays of data; you can also look at libraries like Immutable.js which implement proper immutable data structures.
Thanks, very helpful.
Good points here; learning React myself & coming from jQuery this helps understand how React is meant to work. 👍🏽👍🏽