You can use CoffeeScript classes with React - pros and cons
… and check why 5600+ Rails engineers read also this
You can use CoffeeScript classes with React - pros and cons
One of the big advantages of React components is that they are easy to write. You create an object literal and provide functions as fields of your object. They you pass this object to a React.createClass
function.
In the past React.createClass
was a smart piece of code. It was responsible for creating a component’s constructor and instantiating all fields necessary to make your plain object renderable using React.renderComponent
. It was not an idiomatic JavaScript at all. Not to mention it broke the basic SRP principles.
It changed with a 0.12 version of React. React developers took a lot of effort to improve this situation. A new terminology was introduced. React.createClass
now does a lot less.
One of the most important change for me is that now you can use CoffeeScript classes to create React components. Apart from the nicer syntax, it makes your code more idiomatic. It emphasizes the fact that your components are not a ‘magic’ React thing, but just CoffeeScript objects. I want to show you how you can use the new syntax - and what are pros and cons of this new approach.
A bit of theory - new terminology explained
Starting from React 0.12 the new terminology is introduced. There are now elements - they are an intermediary step between component classes and components. Since before 0.12 children
type was not formally specified, we have a new term for that - it is a node now.
There is also a fragment concept introduced, but it is beyond the scope of this blogpost - you can read more about it here.
As I said before, previously React.createClass
made a lot of things. It made your object renderable by adding private fields to an object passed. It made a constructor to allow passing props
and children
to create a component.
Now all this functionality is gone. React.createClass
now just adds some utility functions to your class, autobinds your functions and checks invariants - like whether you defined a render
function or not.
That means your component classes are not renderable as they are. Now you must turn them into ‘renderable’ form by creating an element. Previously you passed props
and children
to a component class itself and it created an element behind the scenes. This constructor created by React.createClass
now needs to be called by you explicitly. You can do it calling React.createElement
function.
{div, h1} = React.DOM
GreetBox = React.createClass
displayName: 'GreetBox'
render: ->
div null,
h1(key: 'header', @props.children)
children
React.render(GreetBox(name: "World", "Lorem ipsum"), realNode) # Error!
element = React.createElement(GreetBox, name: "World", "Lorem ipsum")
React.render(element, realNode)
React elements can be passed to render a component. Component classes can’t be rendered. You create elements from your component classes.
This is a signature of the React.createElement
function:
React.createElement(type, props, children)
Where type
can be a string for basic HTML tags ("div"
, "span"
) or a component (like in the example above). props
is a plain object, and children
is a node.
A node can be:
- an element (
div(...)
) - array of *node*s (
[div(...), 42, "foo!"]
) - a number (
42
) - a text (
"foo!"
)
Node is just a new fancy name for arguments for
children
you know from previous versions of React.
This is a bit verbose way to create elements from your component classes. It also prevents you from an easy upgrade to 0.13 if you are not using JSX (we got this process covered in our book). Fortunately, with a little trick you can use your old Component(props, children)
style of creating elements.
React provides us React.createFactory
function which returns a factory for creating elements from a given component class. It basically allows you to use the ‘old’ syntax of passing props
and children
to your component classes:
Component = React.createClass
displayName: 'Component'
render: ->
React.DOM.div("Hello #{@props.name}!")
component = React.createFactory(Component)
React.render(component(name: "World"), realNode)
Notice that you can still use React.DOM
like you’ve used before in React. It is because all React.DOM
component classes are wrapped within a factory. Now it makes sense, isn’t it?
Also, JSX does the all hard work for you. It creates elements under the hood so you don’t need to bother.
<MyComponent /> # equivalent to React.createElement(MyComponent)
There is a trend in the React development team to put backwards compatibility into the JSX layer.
All those changes made possible to have your component classes defined using a simple CoffeeScript class. Moving the responsibility of “renderable” parts to createElement
function allowed React devs to make it happen.
React component class syntax
If you want to use class syntax for your React component classes in ES6, it is simple.
Your old component:
ExampleComponent = React.createClass
getInitialState: ->
test: 123
getDefaultProps: ->
bar: 'baz'
render: ->
render body
Becomes:
class ExampleComponent extends React.Component
constructor: (props) ->
super props
@state =
test: 123
@defaultProps: ->
bar: 'baz'
render: ->
render body
Notice that getInitialState
and getDefaultProps
functions are gone. Now you set initial state directly in a constructor and pass default props as the class method of the component class. There are more subtle differences like that in class approach:
getDOMNode
is no more - if you’re usinggetDOMNode
in your component’s code it’s no longer available with component classes. You need to use newReact.findDOMNode
function.getDOMNode
is deprecated, so you shouldn’t use it regardless of using the class syntax or not.- There is no way to pass mixins to component classes - this is a huge drawback. Since there is no idiomatic way to work with mixins in classes (both ES6 and CoffeeScript ones), React developers decided to not support mixins at all. There are interesting alternatives to mixins in ECMAScript 7 - like decorators, but they are not used so far.
- it handles
propTypes
andgetDefaultProps
differently -propTypes
andgetDefaultProps
are passed as a class methods of your component class (as in the example above). - component functions are not auto-binded - in
createClass
React performs auto-binding for all component’s functions. Since now we’re working with a plain CoffeeScript, you got a full control overthis
binding. You can use fat arrows (=>
) to auto-bind tothis
.
As you can see, this approach is more ‘CoffeeScript’-y than React.createClass
. First of all, there is an explicit constructor you write by yourself. This is a real plain CoffeeScript class. You can bind your methods by yourself. Syntax aligns well with a style of typical CoffeeScript codebase.
Notice that you are not constructing these objects by yourself - you always pass a component class to createElement
function and React.render
creates component objects from elements.
Pros:
- It’s a plain CoffeeScript class - it is a clear indication that your components are not ‘special’ in any means.
- It uses common idioms of CoffeeScript.
- You got more control - you control binding of your methods and you are not relying on auto-biding React performs with the
createClass
approach. - Interesting idioms are getting created - CoffeeScript in React is not as common as we’d like, but ECMAScript 6 enthusiasts are creating new interesting idioms. For example things like higher-order components.
Cons:
- Some features are not available now - React developers priority with 0.13 version was to allow common language idioms be used in creating React component classes. They dropped mixins support since they can’t see a suitable idiomatic solution. You can expect they will be reintroduced somehow in later versions of React.
- Developer needs to know more about JS/Coffee - since React does not auto-bind methods in a class approach, you need to be more careful with it. A good understanding of how JavaScript/CoffeeScript works can be necessary to avoid bugs in your components.
- No
getDOMNode
can be a surprise - I believe it’ll be an exception, but you need to be careful using available API. Now inReact.createClass
you can usegetDOMNode
, but not in a component class. I believe APIs will get aligned in next versions of React.
Summary:
Pure classes approach brings React closer to the world of idiomatic Coffee and JavaScript. It is an indication that React developers does not want to do ‘magic’ with React component classes. I’m a big fan of this approach - I favor this kind of explicitness in my tools. The best part is that you can try it out without changing your current code - and see whether you like it or not. It opens a way for new idioms being introduced - idioms that can benefit your React codebase.
“Rails meets React.js” gets an update!
We are going to release a “Rails meets React.js” update with all code in the book updated to React 0.13 this Friday. All people who bought the book already will get this update (and all further updates) for free. It is aimed for Rails developers wanting to learn React.js by example.
For the price of $49 you get:
- 150~ pages of hands-on examples, basic theorethical background, tips for testing and best practices;
- 50~ pages of bonus content - examples of React in action, more advanced topics and interesting worldviews about creating rich frontends;
- a FREE repository of code examples bundled with the book, so you can take examples from the book and fiddle with them;
Interested? Grab a free chapter or watch a quick, 3-minute overview of it now. You can buy the book here. Use V13UPDATE code to get a 25% discount!
Join the group of 350+ happy customers who learned how to build dynamic user interfaces with React and Rails!