Engineering

Componentizing the Kibana UI, Part 2: Writing JavaScript with React

Components in JavaScript

This series is about how the Kibana team is componentizing the Kibana UI. In the last post in this series, I outlined how we've been applying this process to Kibana's CSS. But components are more than just CSS -- they're JavaScript, too. We went looking for a JS library to help us, and when we came upon React we found it to be a natural fit!

React is a library for building user interfaces with JavaScript, open-sourced by Facebook in 2015. In React everything is a component. This is the same fundamental principle which drives our componentization process.

According to the React documentation site, React allows you to build encapsulated components that manage their own state, then compose them to make complex UIs. Lets explore these ideas in the context of our UI Framework.

Encapsulation lets us hide complexity

Engineers should be able to wield a UI Framework component like an artist wields a brush. You should be able to apply it to a medium and, with a little skill, expect a positive result.

As a corollary, an engineer whos using a component shouldnt have to worry about how the component works underneath the hood. Implementation details should be encapsulated, hidden from view. This includes JavaScripty things like state and performance optimizations, but it also means that entire layers of our stack become invisible. The very markup and CSS that make up our components become implementation details, which are encapsulated by our React components.

For example, take a look at how we converted our Button component to React (check out the pull request if you're interested in the details). Heres what the component looked like originally:

<button class="kuiButton kuiButton--basic">
  Basic button
</button>

We just wrapped this markup inside of a React component, like so:

const KuiButton = ({
  className,
  type,
  children,
}) => {
  return (
    <button
      className={getClassName({
        className,
        type,
      })}
    >
      {children}
    </button>
  );
};

And now engineers can use this component like this:

<KuiButton
  type="basic"
  onClick={() => window.alert(&apos;Button clicked&apos;)}
 >
   Basic button
 </KuiButton>

By relegating the actual CSS and markup to mere implementation details, we gain three big advantages:

  1. The code that represents a user interface becomes more readable and succinct.
  2. Engineers no longer need to remember class names and specific markup. Instead, they can focus on wiring the UI with event handlers and business logic to bring the UI to life. This means greater code consistency, fewer hacks and custom solutions, and a faster development cycle.
  3. Engineers who maintain the UI Framework gain a greater degree of freedom when refactoring components -- we can rename classes and restructure a components markup without worrying about disrupting someone elses workflow.

Componentization means composition

Good components can be defined by many traits. They should be modular, reusable, and ideally adhere to the single responsibility principle. But great components are also composable.

This means that many components can be thought of as simple containers, into which any other component or number of components can be injected. In the previous blog post in this series, I raised the example of a Panel component. This component is just a box with a title. And like any box, it can contain literally anything: text, buttons, a table, or even other Panels. A component is composable when it exhibits this level of flexibility.

You can put anything inside of a Panel

The process of componentizing our user interfaces largely consists of figuring out how to compose them out of components. When we look at a complex user interface that needs to be broken apart into components, we start by looking for the containers. And typically, these are the UI elements that become we can extract into components in our UI Framework. By following this simple process, we end up with an ecosystem of components which can be reused and composed together to create complex UIs.

React emphasizes composition as a first-class concept. It encourages component developers to design components to be stateless and functional. This results in components which accept dependencies, most notably the children dependency. React supports composition by allowing you to provide components as the children dependencies of other components. The React team has written a terrific article on how they designed React with composition in mind, entitled Composition vs Inheritance. If youre getting started with React, I highly recommend you check it out.

I should mention that I also love stateless, functional React components because theyre so easy to demonstrate in the UI Framework. In fact, I copied the above KuiButton code snippet from our UI Framework documentation site. Unlike an AngularJS directive, which would require creating an Angular app, registering the directive to it, and creating a template in which to declare an instance of the directive, theres very little overhead required to instantiate a React component. You just import it and instantiate it with whatever JavaScript dependencies it requires.

This also makes React components very easy to unit test. We use AirBnBs Enzyme to render an HTML string of the component, and then we use Facebook's Jest to save this string as a snapshot, and compare it against subsequent renders.

Heres what a test would look like:

import React from &apos;react&apos;;
import { render } from &apos;enzyme&apos;;

import { KuiButton } from &apos;./button&apos;;

describe(&apos;KuiButton is rendered&apos;, () => {
  const $button = render(
    <KuiButton>
      Hello
    </KuiButton>
  );

  expect($button)
    .toMatchSnapshot();
});

This would render a snapshot which looks like this:

exports[`KuiButton Baseline is rendered 1`] = `
<button
  class="kuiButton"
>
  <span
    class="kuiButton__inner"
  >
    <span>
      Hello
    </span>
  </span>
</button>
`;

This snapshot is committed to the repo. During subsequent executions of our UI Framework test suite, Jest will detect that the snapshot exists and compare Enzymes output against it. If the output doesnt match the snapshot, well see a diff in the terminal with some helpful error messages. When this happens, we can either decide that our changes to the component are ones we want to preserve, in which case well update the snapshot with the latest output, or well realize we made a mistake and fix the component so that the tests pass.

Looking ahead

Were just getting started with the transition to React. If youd like to follow our progress, drop in on our React-related GitHub Issues and see whats in the works. We also created an open-source React Design Workshop to help ourselves and others get started with React. Feel free to run through it and make suggestions on ways we can make it better!

Next up: Designing systems

In the next and final post, Ill share the thrilling secret of how weve applied componentization to our actual design process, so that we end up creating design systems instead of just static mockups.

Interested in joining our team? Well, were interested in you, too. Come say hi!