ReactJS has fundamentally challenged & changed the way I view web UI. The brief time that I've been working with it has been full of "aha!" moments that have rekindled excitement for the web in a way that I haven't felt in years.

Much of that excitement is due to the fact that React views the UI differently that what we're used to. You don't mutate the DOM directly - instead React components render to a virtual DOM. This virtual DOM diffs your new changes and computes the minimum number of steps to update the actual DOM, which is then effectively "patched" only with what changed. (Read more about how React's reconciliation works here and here.)

However, every web UI library/framework I've worked with up to this point mutates the DOM directly. If I want to take advantage of existing UI widgets from other libraries, while still using React, can I do so?

The answer is yes.

React, Meet KendoUI

To demonstrate how, let's take this demo of a KendoUI radial gauge (it includes a KendoUI slider as well) and convert it to React components. You can refer to the linked demo to see how the it works the "non-React" way with KendoUI and jQuery. (I should mention that the Radial Gauge is part of KendoUI Professional - which is a paid version of KendoUI that includes a number of more sophisticated widgets/visualizations in addition to what is part of the open-source KendoUI core. See this for more info on what's in Professional vs Core.)

Radial Gauge

First, let's create a RadialGauge component that simply renders a div to the DOM (since, after all, the KendoUI widget will target that element once we're done):

// react 0.11 supports namespaced components :)
var Kendo = {};

Kendo.RadialGauge = React.createClass({  
  render: function() {
    return <div className="gauge" />;
  }
});

Don't, worry, it doesn't stop there. :)

React components have several lifecycle methods, one of which is componentDidMount. This method is invoked just after a component has rendered for the first time. It’s at this point that we can mutate the DOM. We can use the getDOMNode helper method to get a reference to the div we're rendering:

Kendo.RadialGauge = React.createClass({  
  componentDidMount: function() {
    $(this.getDOMNode()).kendoRadialGauge({
      theme: "silver",
      pointer: {
        value: 88
      },    
      scale: {
        minorUnit: 5,
        startAngle: -30,
        endAngle: 210,
        max: 180
      }
    });
  },

  render: function() {
    return <div className="gauge" />;
  }
});

Well - that's awful. Our widget options are hard-coded. We can, instead, pass those option values as props, and set default values for them like I'm doing below in getDefaultProps:

// just showing the relevant methods
Kendo.RadialGauge = React.createClass({  
  getDefaultProps: function() {
    return {
      theme: "silver",
      minorUnit: 5,
      startAngle: -30,
      endAngle: 210,
      max: 180
    }
  },

  componentDidMount: function() {
    var props = this.props;
    $(this.getDOMNode()).kendoRadialGauge({
      theme: props.theme,
      pointer: {
        value: props.value
      },    
      scale: {
        minorUnit: props.minorUnit,
        startAngle: props.startAngle,
        endAngle: props.endAngle,
        max: props.max
      }
    });
  },
  // other methods, etc.
});

OK, this is looking better, but we have one last hurdle.

It's important to realize that since we're mutating the DOM after the component renders, our KendoUI widget does not get the benefit of React's DOM diff-ing. Instead, we need to listen for when changes that could affect the widget occur and respond appropriately.

If the props.value changes, we need to know – and it could, of course, require re-rendering the widget. Both the componentWillReceiveProps and componentDidUpdate lifecycle methods are opportunities to respond to our props.value change. If we were to simply re-render KendoUI radial gauge, we'd lose the chance to animate the movement of the dial from its current value to the new one. Here's what we have so far:

Kendo.RadialGauge = React.createClass({  
  getDefaultProps: function() {
    return {
      theme: "silver",
      minorUnit: 5,
      startAngle: -30,
      endAngle: 210,
      max: 180
    };
  },

  componentDidMount: function() {
    var props = this.props;
    $(this.getDOMNode()).kendoRadialGauge({
      theme: props.theme,
      pointer: {
        value: props.value
      },    
      scale: {
        minorUnit: props.minorUnit,
        startAngle: props.startAngle,
        endAngle: props.endAngle,
        max: props.max
      }
    });
  },

  componentWillReceiveProps: function(nextProps) {
    if(nextProps.value !== this.props.value) {
      $(this.getDOMNode()).data("kendoRadialGauge").value(nextProps.value);
    }
  },

  render: function() {
    return <div className="gauge" />;
  }
});

In componentWillReceiveProps we're setting the value on the widget if it has changed, and KendoUI will animate the change in the dial for us.

The RadialGauge component, from React's perspective, just renders a div, so diffing it any time the props change is neglible performance-wise. (The real change is the conditional update of the KendoUI widget in componentWillReceiveProps.) However - it would be wise to implement shouldComponentUpdate if your component's render method has higher level of complexity.

Slider

Now let's create a component for the slider. The first part – rendereding a div and applying the widget in componentDidMount – will look familiar:

Kendo.Slider = React.createClass({  
  getDefaultProps: function() {
    return {
      min: 0,
      max: 180,
      showButtons: false
    };
  },

  componentDidMount: function() {
    var props = this.props;
    $(this.getDOMNode()).kendoSlider({
      min: props.min,
      max: props.max,
      showButtons: props.showButtons,
      value: this.props.value
    });
  },

  render: function() {
    return <div />;
  }
});

It gets a little different here.

Events

Our slider's value can be changed by the user, and we need to handle that change. Since we're dealing with an event generated outside of React's synthetic events system, we need to provide an event handler to the KendoUI widget. It's also important for us to clean up (dispose of) that event handler when the component is removed from the DOM – which we can do in the componentWillUnmount method.

Mixins

I'm also taking advantage of React's support for component mixins in our Slider component. You have a few options when it comes to a child control (like our slider) notifying other controls of a change in value. I'm using a messaging mixin that enables my component to publish the change in value – and any interested components can subscribe to be notified:

Kendo.Slider = React.createClass({

  mixins: [React.postal],

  getDefaultProps: function() {
    return {
      min: 0,
      max: 180,
      showButtons: false
    };
  },

  componentDidMount: function() {
    var props = this.props;
    $(this.getDOMNode()).kendoSlider({
      min: props.min,
      max: props.max,
      showButtons: props.showButtons,
      value: this.props.value,
      change: this.handleChange
    });
  },

  componentWillUnmount: function() {
    $(this.getDOMNode()).data("kendoSlider").destroy();
  },

  handleChange: function(e) {
    this.publish("change.speed", { speed: e.value });
  },

  render: function() {
    return <div />;
  }
});

Notice that we're also calling destroy() on our KendoUI widget in the component's componentWillUnmount method. It's important to clean up after ourselves when integrating other UI tools.

Taking it a Step Further

Why stop at just the gauge and slider? I'd like to show this alongside "traditional" React components, so I've created a dropdown component to let you pick between two different units of measure, and a display component that shows the value & unit of measure below the gauge.

UnitOfMeasure Component

The UnitOfMeasure component makes use of the messaging mixin, since I'm using that to publish any change in the selected value.

var UnitOfMeasure = React.createClass({  
  mixins: [React.postal],

  handleChange: function(e) {
    this.publish("change.uom", {
      uom: $(this.getDOMNode()).val()
    });
  },

  render: function() {
    return <select className="uom-select" onChange={this.handleChange}>
      <option>MPH</option>
      <option>KPH</option>
    </select>;
  }
});

SpeedDisplay Component

This component isn't much to speak of. Its only function is to render text inside a div.

var SpeedDisplay = React.createClass({  
  render: function() {
    return <div className="speed-display">
      { this.props.speed + " " + this.props.uom }
    </div>;
  }
});

Putting it All Together

The KendoExample component owns the mutable state for our example - which consists of the speed and uom values. This component is interested in the messages published by our UnitOfMeasure and Kendo.Slider components, and it will call setState when one of those messages arrive.

var KendoExample = React.createClass({

  mixins: [React.postal],

  getInitialState: function() {
    return {
      speed: 88,
      uom:"MPH"
    }
  },

  componentWillMount: function() {
    this.subscribe("change.#", function(data) {
      this.setState(data);
    });
  },

  componentWillUnmount: function() {
    this.disposeSubscriptions();
  },

  render: function() {
    return <div>
      <div className="container">
        <Kendo.RadialGauge value={this.state.speed} />
        <Kendo.Slider value={this.state.speed} id="gauge-slider" channel={this.props.channel} />
        <UnitOfMeasure channel={this.props.channel} />
      </div>
      <SpeedDisplay speed={this.state.speed} uom={this.state.uom} />
    </div>;
  }
});

The result:

Wrapping Up

I hope you found this helpful! If you're interested in seeing more examples of React integration with other libraries, check out React Bootstrap and Wingspan Forms.

Update

Christopher Chedeau (one of the many talented developers working on React) was kind enough to review this post and pointed out that - in the RadialGauge component - he would utilize componentWillReceiveProps to compare the new props with old, and conditionally apply the DOM mutation(s) there if the values had changed. I've since updated my example to show this approach, rather than the original implementation that handled updating the radial gauge in componentDidUpdate. The React team has been friendly, responsive and helpful whenever I've asked questions - so don't be afraid to reach out for input.

(Thanks, Christopher - much appreciated!)