The react tutorial for riot

This tutorial is a copy of the features from the react tutorial about building a comment section, but then for riot. Starting with react, it felt utterly complicated for small use cases and riot is a perfect, minimalistic alternative. Here I show you how it works.

NB. This tutorial is written in January 2015, when riot 2.2 was still a thing. In November 2016 riot 3.0 was released. Some parts in this tutorial might be broken when you use riot 3.*.

The react tutorial builds a comment section with a few individual building blocks:

- CommentBox
  - CommentList
    - Comment
  - CommentForm

All these blocks will be built using riot with custom tags. To get an idea what this tutorial is about and you are unfamiliar with react, you should read the react tutorial first. If you want to jump to the conclusion, the riot files are available in a gist on GitHub.

Custom tags as building blocks

Let's start with a file comments.tag which will hold all custom tags for riot.

<comment-box>
  <h1>Comments</h1>

  <comment-list comments={ comments } />
  <comment-form />

  this.comments = []
</comment-box>

<comment-list>
  <comment each={ opts.comments } />
</comment-list>

<comment-form>
  <form>
    <input type="text" name="name">
    <textarea name="message"></textarea>
    <input type="submit" value="Post">
  </form>
</comment-form>

<comment>
  <div>
    <h2>My name</h2>
    <p>My message</p>
  </div>
</comment>

You might notice the syntax is like react JSX, as it is XML-ish with HTML and template-like sugar mixed. Foremost, it is important to notice all individual building blocks are like custom HTML tags: <comment-box> and so on.

A comment box is composed of a header, a list of comments and finishes with a form. A comment list is a block where the comment block is repeated for every comment there is. The comment itself contains the name of the author and the message s/he wrote.

The common pattern for riot tags is "HTML" first, javascript expressions later. The "HTML" can contain pure HTML, some riot-javascript expressions and other riot custom tags.

<my-tag>
  <h1>Some HTML</h1>
  <input name="foo" checked={ enabled } />
  <child-tag />

  var enabled = true;
</my-tag>

Mount the components

Riot uses the term "mount" to attach the custom riot tags to your HTML page. The tag you want to load (in our case, the <comment-box>) is placed in your HTML and riot.mount() is called:

<html>
  <head>
    <title>Hello riot</title>
    <script src="http://cdn.jsdelivr.net/riot/2.0/riot.min.js"></script>
  </head>

  <body>
    <comment-box></comment-box>

    <script src="comments.js"></script>
    <script>riot.mount('comment-box')</script>
  </body>
</html>

The comment box is placed in the body and riot mounts the custom tag to the DOM. There is one final step to make this work...

Compile the riot tag

The riot component definition started with a comments.tag and the HTML loads a comments.js. Riot must compile the tag file into plain javascript, using the riot compiler.

Install the compiler first:

npm install -g riot

Then use riot to compile the tag file:

riot path/to/comments.tag path/to/comments.js

Now use your browser to check the comment form! Only the form, you say? Yes, because there are no comments defined anywhere, the list is not populated and therefore completely absent.

Use options and state

One of the features of riot is to pass on options and capture state of the component. The options can be set at riot.mount() or inside a component and they allow both to "push down" options to child components.

For example, take the <comment-box>'s header, which you can define during mount:

<comment-box>
  <h1>{ opts.title }</h1>
</comment-box>
riot.mount('comments-box', {title: 'Comments'});

All options which are passed onto the component from the caller, are containerized inside the opts. This holds true for child components too, which can obtain parameters via tag attributes (in this example, the comments):

<comment-box>
  <h1>{ opts.title }</h1>
  <comment-list comments={ comments } />

  this.comments = []
</comment-box>

<comment-list>
  console.log(opts.comments)
</comment-list>

Component's state

In above example there is already some state hidden in the code. The "list" of comments is created inside the comment box. Holding state is one of the primarily concepts of riot (and react). When the state is updated, the "view" part is modified internally to comply with the new state.

Like react, riot has a virtual DOM. When the state changes, the virtual DOM updates itself. Next, riot compares the virtual DOM to the actual DOM of your document and updates all changes in bulk. This makes changes to the user interface very efficient.

Now let's build a comment section which keeps the state of comments in the browser's memory (causing all comments to disappear when you refresh the page). One of the first things is to let the comment box allow to add new comments:

<comment-box>
  <h1>{ opts.title }</h1>

  <comment-list comments={ comments } />
  <comment-form />

  this.comments = [];

  add(comment) {
    comments.push(comment)
    this.update()
  }
</comment-box>

<comment-form>
  <form onsubmit={ add }>
    <input type="text" name="name"
    <textarea name="message"></textarea>
    <input type="submit" value="Post">
  </form>

  add(e) {
    var comment = {
      name: this.name.value,
      message: this.message.value
    }

    this.parent.add(comment)
    e.target.reset()
  }
</comment-form>

<comment-list>
  <comment each={ opts.comments } name={ this.name } message={ this.message } />
</comment-list>

<comment>
  <div>
    <h2>{ opts.name }</h2>
    <p>{ opts.message }</p>
  </div>
</comment>

There are a few things happening here. First, the form has a submit handler, called add. When the form is submitted, the handler is executed. Riot directly calles e.preventDefault() which will cause the default action to halt (in this case, the form real submission).

Inside the add function (a ES6 notation which riot compiles into this.add = function()) the syntax this.name and this.message is used. This is a riot feature where all tags with a name or id attribute can be easily identified. Because the input and textarea holds both a name (respectively "name" and "message") you can access their values with this.name.value and this.message.value.

Then, this.parent is accessed. The parent key is the component's parent which is (duh) the parent of the comment-form. In fact, that is the comment-box. So this.parent.add() calls the add() method from the comment-box. This way, it allows you to change the state in the comment box from the comment form.

As third you might notice e.target. The e is the event object which is triggered on submit. This object holds a few properties and one is the target. Simply said, the tag which triggered the event (in this case, the form) is referenced via e.target. In the case of a form submission, it allows you to clear the form when a comment is "posted".

The last part is the comment-box's add(). In here, two things happen. The state of comments is updated (simply push a new block onto the list of comments) and this.update() makes sure riot updates the virtual DOM and pushes the changes into the "real" DOM.

Load and post comments

The final part is to use a server and persist the comments between different requests. I am not posting the server part, the react tutorial has some excellent servers written and they are available in their repositories. Please note this tutorial uses a "name" and "message", but the react tutorial uses an "author" and "text". Update the server code if neccessary to use the new variable names.

There are two moments you need to create HTTP requests: to load comments and to post a new comment. Loading comments will happen at mount and then at a specified interval. So let's start with loading comments:

<comment-box>
  <h1>{ opts.title }</h1>

  <comment-list comments={ comments } />
  <comment-form url={ opts.url } />

  this.comments = []

  add(comment) {
    this.comments.push(comment)
    this.update()
  }

  load() {
    var request = new XMLHttpRequest, self = this

    request.open('GET', opts.url, true)
    request.onload = function () {
      if (this.status >= 200 && this.status < 400) {
        self.comments = JSON.parse(request.responseText)
        self.update()
      }
    }
    request.send()
  }

  load()
  setInterval(this.load, opts.interval)
</comment-box>

A simple XMLHttpRequest is executed (note, IE10+ syntax!). If the request is a success, the comments state is set again and an update is executed. This loading happens when the component is created and thereafter repeatedly after a specified interval. Note you have to add two new options (the url and interval):

riot.mount('comment-box', {
  title:    'Comments',
  url:      '/comments.json',
  interval: 2000
}

Post a comment optimistically

The second XMLHttpRequest is when the form is submitted. Previously we called this.parent.add(comment), but now we're using the comment object to create a POST to the server as well. I called it "optimistically" because just before we are creating the request, the comment is already placed. This way, the user does not have to wait for the request to finish.

<comment-form>
  <form onsubmit={ add }>
    <input type="text" name="name">
    <textarea name="message"></textarea>
    <input type="submit" value="Post">
  </form>

  add(e) {
    var comment = {
      name:    this.name.value,
      message: this.message.value
    }

    this.parent.add(comment)
    this.post(comment, e.target)
  }

  post(comment, form) {
    var data = new FormData
    data.append('name', comment.name)
    data.append('message', comment.message)

    var request = new XMLHttpRequest()
    request.open('POST', opts.url, true)
    request.onload = function () {
      if (this.status >= 200 && this.status < 400) {
        form.reset()
        // You might want to flash a message now
      }
    }
    request.send(data)
  }
</comment-form>

A second method, post(), is defined to encapsulated the XMLHttpRequest. The request simply POST to the specified URL with the given form data. Note FormData is IE10+ only, so you might want to use a compatibility layer for older browsers.

In this new piece of code, the form is only reset after the message is sent successfully. The other parts are untouched (yah, components!).

The cool thing now is you can test the requests. Fire up two tabs in your browsers. Create a comment in one of them and notice how it pops up in the other one too.

Conclusion

I hope you like riot as much as I do. It takes the component way of thinking from react (and polymer) but without a great amount of boilerplate code. It is easy to read and simple to use. And another great fact, it is fast! The library is also 24 times smaller than react.

Of course, react has much more to offer and is therefore way more complicated. Personally, I did not need the additional features. I just wanted to have small components for comments and live search (both coming soon!). For these small parts of a page, riot is a perfect choice.

The last thing is to put all code combined into a single comments.tag file. I put it for you in a gist on GitHub. This way you can easily compare the differences with the react version of a comment box.

One more thing...

As you might notice I didn't do anything about validation and so on. No request.onerror = function () {}. No else when the server did report an error in the 4xx or 5xx range. No validation if all form fields are populated, etcetera etcetera.

Note this is a starters tutorial about how to use riot. If you want to use it in production, add some more validation, checks and messages. Be nice for your users.

Disclaimer

I am not a javascript developer, but rather a back-end (non-node.js) developer. This is the reason I choose for riot (due it's easier syntax) but nevertheless there might be some oddities in above code. If you know more about javascript than me (which is quite likely) just let me know, I will update this tutorial accordingly.