Building a tabs component with React and Foundation

After reading an interesting article and watching this excellent talk from JsConf a couple of days ago, I've decided to look more closely at React. I've dismissed it when it came out, as it seemed to be flying in the face of everything we know about building the front-end (and it very much does!). But after going through the great documentation, tutorial and finally building a simple component myself I can see that there is some method to this madness. Coming from AngularJS, I've found that things React's concepts are a bit easier to reason about (by design). Worth noting, React has a much smaller scope then Angular and is supposed to only be the V in MV* frameworks.

I've decided to do a simple tabs component. This tutorial is quite exploratory, as React has some quirks you need to adhere to. Our base template will look like this:

<!DOCTYPE html>
<html>
  <head>
    <link rel="stylesheet" href="http://cdnjs.cloudflare.com/ajax/libs/foundation/5.0.2/css/foundation.min.css">
    <script src="http://fb.me/react-0.8.0.min.js"></script>
    <script src="http://fb.me/JSXTransformer-0.8.0.js"></script>
    <style>
      /* so we can see our component better */
      body { padding: 100px; }
    </style>
  </head>
  <body>
    <div id="content"></div>
    <script type="text/jsx">

      // @jsx React.DOM
      // the codes will go here
  </body>
</html>

We're going to use Foundation for our CSS. The two scripts are React's source and the JSX transformer, an optional part of React that lets you write XML like syntax inside of javascript. It's not supposed to be used in production - the React team provides an npm tool to precompile templates.

Notice that the type of the script tag is text/jsx and the comment /** @jsx React.DOM */. This is needed for our script to be parsed correctly.

Looking at the Foundation docs we need a structure more or less like this for our tabs to get correct styling.

<dl class="tabs" data-tab>
  <dd class="active"><a href="#panel2-1">Tab 1</a></dd>
  <dd><a href="#panel2-2">Tab 2</a></dd>
  <!-- ...more tabs... -->
</dl>
<div class="tabs-content">
  <div class="content active" id="panel2-1">
    <p>First panel content goes here...</p>
  </div>
  <div class="content" id="panel2-2">
    <p>Second panel content goes here...</p>
  </div>
  <!-- ...more panels... -->
</div>

We don't really need the data-tab attribute or specific links. Let's add some React code to our script tag.

var Tabs = React.createClass({
  render: function() {
    return (
      <dl class="tabs">
        <dd class="active">
          <a href="#">Tab 1</a>
        </dd>
        <dd>
          <a href="#">Tab 2</a>
        </dd>
      </dl>
      <div class="tabs-content">
        <div class="content active"> 
          <p>First panel content goes here...</p> 
        </div>
        <div class="content">
          <p>Second panel content goes here...</p>
        </div>
      </div>
    );
  }
});

React.renderComponent(
  <Tabs />,
  document.getElementById('content')
);

React.createClass creates a new React component that's going to get stored in a variable. Inside, we specify the only required function render. And there we have a cleaned up Foundation tabs markup.

React.renderComponent does as it says on the tin. The first argument is our component (note the XML syntax again) and second is the raw DOM node that the component will get inserted into.

We open our file and ... nothing. The above code has one mistake. The render function requires that we have only one root node, while in the above, we have two (the dl and div). So we need to add a div.

...
  render: (
    <div> <!-- new opening div -->
      <dl class="tabs">
...
      </div>
    </div> <!-- new closing div -->
...

Try again and we have something. If you look into your browser dev tools, it seems that React stripped out our CSS classes. This is because these are reserved keywords in javascript, so we need className instead. Read more here. Let's update that as well.

...
      <dl className="tabs">
        <dd className="active">
...

Yay! This looks much more like the Foundation tabs. Made in pure XMLish Javascript. Right. Ekhm. Let's move on to the good parts. Let's make this work.

First, dumping all your content inside your javascript is not very maintanable. We want to separate our content from our component. We need a way to pass data around.

Second, we need to switch panels when tabs are clicked. We need some notion of state.

Let's make a simple array with our content.

var data = [
  { title: "Tab 1", content: "I am the content of the first tab." },
  { title: "Tab 2", content: "I am the content of the second tab." },
  { title: "Tab 3", content: "Third tab, buddy." }
];

To pass this content into our component we need to use props. These are React's way of passing immutable data from a parent to child (node, component).

React.renderComponent(
  <Tabs data={data} />,
  document.getElementById('content')
);

The braces let us evaluate expressions inside the JSX templates. So the above passes the data variable to components props property and can be accessed with this.props.data inside of our component definition.

<dl className="tabs">
  {this.props.data.map(function (tab) {
    return (
      <dd>
        <a href="#">{tab.title}</a>
      </dd>
    )
  })}
</dl>

Here we again use the {} to evalute an expression. The rest is just javascript. We use map to create a new array of React components. {tab.title} gives us the title from data variable. Neat! Try to do the same for the content panels - the difference will be only in the HTML and the accessed property.

Now let's handle clicks. React introduces the notion of state for your components. state is private to the componets and can be changed with setState. With this call, the component re-renders itself. For us, state will keep the index of the tab.

var Tabs = React.createClass({
  getInitialState: function() {
    return {activeTab: 0};
  },
  handleClick: function(index) {
    this.setState({activeTab: index});
    return false;
  },
  render: function() {
  //...more codes...

getInitialState is part of React's API and will set a inital value. handleClick is our click handler which will take the index of the tab and change the activeTab value (and return false to prevent the browser default on <a> tags).

{this.props.data.map(function (tab, index) {
  var activeClass = this.state.activeTab === index ? 'active' : '';

  return (
    <dd className={'tab ' + activeClass} >
      <a href="#" onClick={this.handleClick.bind(this, index)}>{tab.title}</a>
    </dd>
  )
}, this)}

The map method exposes the index of the current element. We can compare that to the state of our component by accessing this.state.activeTab. If it's true, we are going to add the active CSS class.

On the <a> tag we are going to add an onClick attribute. React proxies DOM events so it can work even on IE8. We add our handler there, accessing it on the this variable. There is small javascript gotcha here though. Because we are in a anonymous function, the value of this is actually window (if we are working in the browser). We can change this by passing this as the second argument to map.

We also need to pass index to handleClick. Again, native (ok, modern) javascript to the rescue! We can use bind to pass arguments to the function when it will be called. The first argument is the context that function is supposed to have, so we pass this.

And ... that's it. The whole component looks like this:

/** @jsx React.DOM */

var data = [
  { title: "Tab 1", content: "I am the content of the first tab." },
  { title: "Tab 2", content: "I am the content of the second tab." },
  { title: "Tab 3", content: "Third tab, buddy." }
];

var Tabs = React.createClass({
  getInitialState: function() {
    return {activeTab: 0};
  },
  handleClick: function(index) {
    this.setState({activeTab: index});
    return false;
  },
  render: function() {
    return (
      <div>
        <dl className="tabs">
          {this.props.data.map(function (tab, index) {
            var activeClass = this.state.activeTab === index ? 'active' : '';

            return (
              <dd className={'tab ' + activeClass} >
                <a href="#" onClick={this.handleClick.bind(this, index)}>{tab.title}</a>
              </dd>
            )
          }, this)}
        </dl>
        <div className="tabs-content">
          {this.props.data.map(function (tab, index) {
            var activeClass = this.state.activeTab === index ? 'active' : '';

            return (
              <div className={'content ' + activeClass}>
                <p>{tab.content}</p>
              </div>
            )
          }, this)}
        </div>
      </div>
    );
  }
});

React.renderComponent(
  <Tabs data={data} />,
  document.getElementById('content')
);

This could probably be DRY'ied up a bit, but the logic is simple at this point and it is quite clear where the component state, HTML and click handlers reside.