Using Kendo UI for jQuery in a React App

Kendo UI for jQuery provides 70+ UI widgets for building complex, enterprise-grade JavaScript applications. However, today's complex JavaScript applications require more than jQuery alone. This is why, at Progress, we are experimenting with completely new, re-written from the ground up, Kendo UI for React components. You can find these new components in Github and on npm, allowing you to experiment along with us.

The Kendo UI for React components are obviously still bubbling in the test tube. This might be the case for much of 2016. I do hope you check them out. But, let's be honest, these KUI React preview components won't help you build a React app today.

In this article, I'm going show how I'd go about wrapping a React component around a Kendo UI for jQuery widget, so that the widget can be used in a React application. But first, let's look at why you would want to do this in the first place.

The Current State of UI Toolkits for React

A React developer who needs a competent and complete enterprise grade UI toolkit today can either create everything from scratch or make use of one of the mature React UI toolkits listed below:

(Note: I don't consider as an option the gluing together of less capable random components from different developers that more than likely would have to be customized to become a viable solution for enterprise grade applications that have to have a long shelf life.)

If a developer chooses to make use of one of the React toolkits listed above, they will likely find that none provide a complete set of robust, enterprise-grade widgets. Some of them do have a nice offering, but none of them come close to the professional offering from Kendo UI for jQuery (e.g. Grid, Spreadsheet, TreeView, GanttChart, Charts etc.).

In addition, none of them offer an official support system. Official support is that tool feature that a lot of developers forget about, or don't value enough up front (but they always value it when things go wrong). Don't be that developer as you select tools. Make sure you can get help when you need it.

What is a developer to do if they want the quality, support, and depth of widgets found in Kendo UI for jQuery, but also need the modern tooling found in the React ecosystem? In other words, what should a React developer do who wants to use Kendo UI for jQuery widgets in a React application? Is that even possible?

In fact, yes, it is possible – maybe not ideal, but totally possible. I'm even aware of developers doing this today for production applications because building something like the Kendo UI for jQuery Grid from scratch isn't realistic.

The fact is, bringing Kendo UI to a tool via a wrapper or bridge isn't all that uncommon. We did it with Angular 1. The Aurelia community recently did it by creating a bridge for Kendo UI so that it would work in Aurelia applications.

kendo_aurelia

Wrapping/bridging can work, and, when nothing less than Kendo UI will do, developers do it.

Through the rest of this article, I'm going show how I'd go about wrapping a React component around a Kendo UI for jQuery widget, so that the widget can be used in a React application. Since Kendo UI for jQuery was added to npm as CJS modules a couple of weeks ago, building a wrapper for React isn't all that difficult.

A Module Loader Is In Use (i.e. webpack)

When building a React application, it is a safe assumption that webpack or something like it will be used. As I describe how to wrap a Kendo UI jQuery widget as a React component, I'll spend almost no time explaining module loading via webpack using the ES2015 Module format. If you are new to webpack, consider studying up on the basics of webpack and ES2015 modules before reading the rest of this article.

The Practice Of Using Non-React Code With React

A great deal of opinions and strategies come along with mashing older non-React code into a React app. React officially prescribes a simplistic solution and offers a basic demo to help developers with this task. Additionally, several React community leaders have offered up strategies as well.

I gleaned some insight from each of these perspectives, but ultimately the solution I'm offering is unique to the nature of a Kendo UI widget. I tried to balance what a React developer would expect (and want) with the reality and nature of the Kendo UI widget and its API. Unfortunately, the solution does not result in what most React developers would consider an ideal React component.

Below is an outline of my thinking and decisions that resulted as I constructed a React wrapper for Kendo UI widgets. Read this carefully, so that you are not left wondering why I did X instead of Y when wrapping non-React code.

  • The name of a wrapped Kendo UI component will be the same name used to invoke the component if you were using jQuery alone (i.e. $().kendoDropDownList()). Except for that fact that the component will start with a capital letter instead of a lower case (e.g. <KendoDropDownList/> not <kendoDropDownList/>) per React standards.
  • Kendo UI wrapped components accept five properties (options, methods, events, unbindEvents, triggerEvents). These properties are used to house and consolidate the Kendo UI widget API. Simply put, options is the typical set of options that are passed to Kendo UI widgets. methods is the name of a method and any arguments to be invoked. events is any events that you would like to be attached to the widget. unbindEvents removes events and triggerEvents triggers widget events. Remember, each time the component containing the Kendo UI wrapper component is re-rendered, each of these props can be changed, and these changes will update the Kendo UI widget.
  • Once a Kendo UI widget is created using React lifecycle callbacks (within the wrapper), it should not be destroyed and re-created each time the state in an application changes (i.e. shouldComponentUpdate: function(){return false;}). The strategy I've chosen is to have the widget created once when the React component is first mounted. Then, after that, the component will only get notified of property changes from above when the state changes. If the properties are different from the previous ones, the widgets API will be used to update the widget.
  • State changes from above should result in a property change for a wrapped Kendo UI widget. However, React state should be used in the wrapped component itself. Because Kendo UI widgets offer their own mechanism for dealing with data (i.e. dataSource) mixing Kendo UI's concept of state with React's did not seem wise. At best, the state of a widget could be kept in a component wrapping the Kendo UI wrapped component, sending the parent state into the Kendo UI wrapped component as a property to be used by the Kendo UI dataSource abstraction (e.g. <KendoDropDownList options={{dataSource:[data]}} />). This means that when the parent state changes, it would allow for property updates, which could be used to change the wrapped component (e.g. .setDataSource()).
  • Create a pattern that could be used to wrap and instantiate any Kendo UI widget.

Wrapping Kendo UI for jQuery in A React Component

The code that follows is the module used within a webpack setup to wrap a Kendo UI kendoDropDownList widget.

I'll start with an outline of the code and then discuss each section of the component definition (i.e. the properties passed to React.createClass()). Read the outline below and the comments.

// import/require dependencies
import kuidropdown from 'kendo-ui-core/js/kendo.dropdownlist.js';
import React from 'react';
import ReactDOM from 'react-dom';
import deepDiff from 'deep-diff';

// create a React component, that is a wrapper for a Kendo UI widget
const KendoDropDownList = React.createClass({

    //component is in the DOM, so do stuff to it in this callback
    componentDidMount: function() {
    },

    //instance methods for updating widget
    triggerKendoWidgetEvents:function(events){
    },
    bindEventsToKendoWidget:function(events){
    },
    unbindEventsToKendoWidget:function(events){
    },
    callKendoWidgetMethods:function(methods){
    },

    //not called on inital render, but whenever parent state changes this is called
    componentWillReceiveProps: function(nextProps){
    },

    //don't run render again, create widget once, then leave it alone
    shouldComponentUpdate: function(){
    },

    //destory it, when the component is unmouted
    componentWillUnmount: function() {
    },

    //use the passed in React nodes or a plain <div> if no React child nodes are defined
    render: function() {
    }
});

//export the wrapped component
export default KendoDropDownList

The outline should make it obvious that we are importing/requiring several dependencies for our component, creating a React component, then exporting the component. I'll address the next section in the outline above from top to bottom starting with componentDidMount.

The first component specification we provide for the wrapper is a function that is called when the component is mounted to the real DOM (i.e. componentDidMount). This is where the Kendo UI DropDownList widget will be instantiated.

componentDidMount: function() {
    //get, child element node for this component
    var elementNode = this.elementNode = ReactDOM.findDOMNode(this);
    
    //determine if a selector was passed on which to invoke the KUI widget
    if(this.props.selector){
        elementNode = elementNode.querySelector(this.props.selector);
    }

    //instantiate and save reference to the Kendo UI widget on elementNode
    //note I am not using jQuery plugin to instantiate, don't want to wait for namespace on $.fn
    this.widgetInstance = new kuidropdown.ui.DropDownList(elementNode,this.props.options);

    //if props are avaliable for events, triggers, unbind events, or methods make it happen now
    this.props.events ? this.bindEventsToKendoWidget(this.props.events) : null;
    this.props.methods ? this.callKendoWidgetMethods(this.props.methods) : null;
    this.props.triggerEvents ? this.triggerKendoWidgetEvents(this.props.triggerEvents) : null;
    this.props.unbindEvents ? this.unbindEventsToKendoWidget(this.props.unbindEvents) : null;
}

Within componentDidMount, after instantiating the widget, a set of custom/instance component methods (e.g. this.bindEventsToKendoWidget) are called to deal with the setup of Kendo UI widgets (methods shown below).

//instance methods for updating widget
triggerKendoWidgetEvents:function(events){
    events.forEach(function(event){//loop over events, and trigger
        this.widgetInstance.trigger(event);
    }, this);
},
bindEventsToKendoWidget:function(events){
    Object.keys(events).forEach(function(event){//loop over events and bind
        this.widgetInstance.bind(event,events[event]);
    }, this);
},
unbindEventsToKendoWidget:function(events){
    events.forEach(function(event){//loop ove revents and unbind
        this.widgetInstance.unbind(event);
    }, this);
},
callKendoWidgetMethods:function(methods){
    Object.keys(methods).forEach(function(method){//loop over methods and call
        this.widgetInstance[method](...methods[method])
    }, this);
}

These methods are called upon when the Kendo UI widget is instantiated and potentially when properties change. They basically take the methods' and events' properties and use the values to invoke the Kendo UI widget API (i.e. bind(), unbind(), trigger(), widget.SomeMethod()).

Using componentWillReceiveProps, I again call the methods upon property changes, as well as option changes if sent and if the widget supports setOptions().

componentWillReceiveProps: function(nextProps){
    //always update the widget with nextProp changes if available
    if(nextProps.events){
        this.bindEventsToKendoWidget(nextProps.events);
    }

    if(this.widgetInstance.setOptions){
        if(nextProps.options){
            this.widgetInstance.setOptions(nextProps.options);
        }
    }

    //try and determine if any of the nextProps have changed, and if so, update the widget
    if(nextProps.methods){
        if(deepDiff(nextProps.methods,this.props.methods)){
            this.callKendoWidgetMethods(nextProps.methods);
        }
    }

    if(nextProps.unbindEvents){
        if(deepDiff(nextProps.unbindEvents,this.props.unbindEvents)){
            this.unbindEventsToKendoWidget(nextProps.unbindEvents);
        }
    }

    if(nextProps.triggerEvents){
        if(deepDiff(nextProps.triggerEvents,this.props.triggerEvents)){
            this.triggerKendoWidgetEvents(nextProps.triggerEvents);
        }
    }
}

Next, I use the shouldComponentUpdate lifecycle event to return false, so that the widget is created once.

//don't run render again, create widget once, then leave it alone
shouldComponentUpdate: function(){return false;},

Then, I use the componentWillUnmount lifecycle event to destroy the Kendo UI widget when React cleans it up.

//destroy it, when the component is unmounted
componentWillUnmount: function() {
    this.widgetInstance.destroy();
}

Last, I define what the component should render to the DOM. A <div/>, on which our Kendo UI widget should be invoked, or a custom set of elements sent in when using the component (more on that in a minute).

//use the passed in React nodes or a plain <div> if no React child nodes are defined
render: function() {
    return this.props.children ? this.props.children : <div/>;
}

Altogether, the pattern for wrapping a Kendo UI widget in a React component will look like this:

// import/require dependencies
import $ from 'jquery';
import kuidropdown from 'kendo-ui-core/js/kendo.dropdownlist.js';
import React from 'react';
import ReactDOM from 'react-dom';
import deepDiff from 'deep-diff';

// create a React component, that is a wrapper for a Kendo UI widget
const KendoDropDownList = React.createClass({

    //component is in the DOM, so do stuff to it in this callback
    componentDidMount: function() {
        //get, child element node for this component
        var elementNode = this.elementNode = ReactDOM.findDOMNode(this);
        
        //determine if a selector was passed on which to invoke the KUI widget
        if(this.props.selector){
            elementNode = elementNode.querySelector(this.props.selector);
        }

        //instantiate and save reference to the Kendo UI widget on elementNode
        //note I am not using jQuery plugin to instantiate, don't want to wait for namespace on $.fn
        this.widgetInstance = new kuidropdown.ui.DropDownList(elementNode,this.props.options);

        //if props are available for events, triggers, unbind events, or methods make it happen now
        this.props.events ? this.bindEventsToKendoWidget(this.props.events) : null;
        this.props.methods ? this.callKendoWidgetMethods(this.props.methods) : null;
        this.props.triggerEvents ? this.triggerKendoWidgetEvents(this.props.triggerEvents) : null;
        this.props.unbindEvents ? this.unbindEventsToKendoWidget(this.props.unbindEvents) : null;
    },

    //instance methods for updating widget
    triggerKendoWidgetEvents:function(events){
        events.forEach(function(event){//loop over events, and trigger
            this.widgetInstance.trigger(event);
        }, this);
    },
    bindEventsToKendoWidget:function(events){
        Object.keys(events).forEach(function(event){//loop over events and bind
            this.widgetInstance.bind(event,events[event]);
        }, this);
    },
    unbindEventsToKendoWidget:function(events){
        events.forEach(function(event){//loop ove revents and unbind
            this.widgetInstance.unbind(event);
        }, this);
    },
    callKendoWidgetMethods:function(methods){
        Object.keys(methods).forEach(function(method){//loop over methods and call
            this.widgetInstance[method](...methods[method])
        }, this);
    },

    //not called on inital render, but whenever parent state changes this is called
    componentWillReceiveProps: function(nextProps){
        //always update the widget with nextProp changes if avaliable
        if(nextProps.events){
            this.bindEventsToKendoWidget(nextProps.events);
        }

        if(this.widgetInstance.setOptions){
            if(nextProps.options){
                this.widgetInstance.setOptions(nextProps.options);
            }
        }

        //try and determine if any of the nextProps have changed, and if so, update the widget
        if(nextProps.methods){
            if(deepDiff(nextProps.methods,this.props.methods)){
                this.callKendoWidgetMethods(nextProps.methods);
            }
        }

        if(nextProps.unbindEvents){
            if(deepDiff(nextProps.unbindEvents,this.props.unbindEvents)){
                this.unbindEventsToKendoWidget(nextProps.unbindEvents);
            }
        }

        if(nextProps.triggerEvents){
            if(deepDiff(nextProps.triggerEvents,this.props.triggerEvents)){
                this.triggerKendoWidgetEvents(nextProps.triggerEvents);
            }
        }
    },

    //don't run render again, create widget once, then leave it alone
    shouldComponentUpdate: function(){return false;},

    //destory it, when the component is unmouted
    componentWillUnmount: function() {
        $(this.elementNode).getKendoDropDownList().destroy();
    },

    //use the passed in React nodes or a plain <div> if no React child nodes are defined
    render: function() {
        return this.props.children ? this.props.children : <div/>;
    }
});

//export the wrapped component
export default KendoDropDownList

Any Kendo UI widget can be wrapped in this pattern and used as a React component. You can do this locally in your own applications or you can make use of the widgets I've already wrapped, and placed in npm (note: the source for these packages is identical to what you see above).

Let's take the Kendo UI for jQuery React wrappers in npm I created for a spin.

Using A Kendo UI React-Wrapped Component

In the process of writing this article, I created a new Kendo UI Boilerplate for working with React wrapped widgets. Let's spin this boilerplate up now.

Head over to the Kendo UI boilerplate GitHub repo (https://github.com/kendo-labs/kendo-ui-boilerplates) and download or clone the repo. Then follow these instructions for spinning up the boilerplate code.

After spinning up the boilerplate you should see the following two KUI dropDownList widgets in your browser.

kendo_components

These widgets are imported into app.js and are then used in the React App component. Below, I am showing just the components in use from within the App component.

<KendoDropDownList
    //only updates upon state change from above if widget supports setOptions()
    //don't define events here, do it in events prop
    options={{ //nothing new here, object of configuration options
        dataSource:data,
        dataTextField: "text",
        dataValueField: "value"
    }}
    //updates if object is different from initial mount
    methods={{ //name of method and array of arguments to pass to method
        open:[], //send empty array if no arguments
        value:[fakeApropChange]
    }}
    //Right now, always updates
    events={{ //name of event, and callback
        close:function(){console.log('dropdown closed')},
        select:function(){console.log('item selected')},
        open:function(){console.log('dropdown opened')}
    }}
    //updates if array is different from initial mount
    unbindEvents={[ //name of event to unbind, string
        "select"
    ]}
    //updates if array is different from initial mount
    triggerEvents={[ //name of event to trigger, string
        "open",
    ]}>
        <input className="kendoDropDownList" />
</KendoDropDownList>

<KendoDropDownList>
    <select>
        <option>S - 6 3/5"</option>
        <option>M - 7 1/4"</option>
        <option>L - 7 1/8"</option>
        <option>XL - 7 5/8"</option>
    </select>
</KendoDropDownList>

As you are likely already aware, the <KendoDropDownList/> React component is just a wrapper around the Kendo UI dropDownList widget. In fact, this boilerplate uses the exact same code we discussed earlier to wrap the dropDownList widget. Go ahead and open the kendoDropDownList.js file in the boilerplate and verify this fact.

This boilerplate is the perfect outline for creating your own wrapped Kendo UI component widgets for a React application. But, imagine if all of the KUI widgets were already wrapped for you and all you had to do was npm install them and use them. As previously stated, I've already wrapped a few and put these them in npm. So, let's remove the locally wrapped widget (i.e. kendoDropDownList.js) and use a few of these npm packages in the boilerplate.

I'm going to assume you still have the webpack and server process from the boilerplate running.

Next, npm remove kendo-ui-core.

> npm remove kendo-ui-core --save

Then, npm install the following packages.

> npm install kendo-ui-react-jquery-colorpicker --save
> npm install kendo-ui-react-jquery-dropdownlist --save

Now, open the app.js file and remove following line

import KendoDropDownList from './kendoDropDownList.js';

Replace it with these two lines:

import KendoDropDownList from 'kendo-ui-react-jquery-dropdownlist';
import KendoColorPicker from 'kendo-ui-react-jquery-colorpicker';

Last, add the <KendoColorPicker /> picker code below to the bottom of the App component render function body, right after that last <KendoDropDownList /> component.

<KendoColorPicker selector="#kuicp"
    style={{marginTop:100}}
    options={{
        value:"#ffffff",
        buttons:false
    }} >
        <div style={{float:'right'}}><div id="kuicp"></div></div>
</KendoColorPicker>

Save app.js and the browser should re-render and show the new React components in use that were installed from npm.

As of today, I've only placed two wrapped components in npm (i.e. <KendoColorPicker /> and <KendoDropDownList />). Do you think I should wrap all the KUI widgets and place then into npm? Tell me your thoughts in the comments.

Kendo UI CSS Files Are Critical

There is a very important detail about these wrappers that I should mention. To use the wrappers, you'll have to import/include the CSS files in the context in which the components are used. For example, in the boilerplate I'm using webpack to import CSS files. Thus, to have the proper CSS on the page so that the widgets function correctly, I imported the Kendo UI CSS files into the app.js like so:

import 'kendo-ui-core/css/web/kendo.common.core.min.css';
import 'kendo-ui-core/css/web/kendo.default.min.css';

If you pull in the npm packages into a development environment not setup like the boilerplate, you'll have to figure out where the CSS files are and how to import them into the context in which you are using the components. If you are using webpack, it will likely be similar to how I did it. Just keep in mind that the npm packages assume you'll make sure the proper CSS files are included in the page.

Final Thoughts

I believe wrapping KUI for jQuery widgets to be a legitimate solution until which point our Kendo UI for React components matches the jQuery offering. In fact, developers have been asking for this information for some time now:

kendo_feedback

Hopefully, the stop gap I've provided here will suffice until Progress offers a true React component for Kendo UI. Until then, don't let that stop you from using Kendo UI in a production React app.

Comments

  • Pingback: Using Kendo UI for jQuery in a React App — Front-End Front()

  • Tony Garcia

    Hi Cody. Thanks a lot for this. We’re going to start using KendoUI for our ReactJS apps and I was trying to figure out the best way to use the components now until more of the KendoUI for React components become available. So the timing on this couldn’t be better!

    • Thanks for the comment. Was really hoping this would aid devs in the trenches trying to do this. I’m not sure my solutions is the best, so I’m open to feedback and contributions via Github. https://github.com/codylindley/k-ui-react-jquery-wrappers

      • Tony Garcia

        Sure thing. Once I start digging deeper I’m sure I’ll have some feedback for you. I haven’t worked with Kendo for a few years, so it will be cool to get re-acquainted with it.

  • Pingback: Dew Drop - August 4, 2016 (#2302) - Morning Dew()

  • Pingback: Curated Links of 6 Aug 16 – DiscVentionsDaily()

  • bpeng

    Hi Cody, is the ReacJS wrapper for Kendo Grid fully working. Try to use it with the sample odata service, got a lot errors, such as kendodroptarget is not a function, data.slice is not a function etc…