Author avatar

Chris Parker

How to Use ReactJS Components as jQuery Plugins

Chris Parker

  • Mar 19, 2020
  • 13 Min read
  • 1,143 Views
  • Mar 19, 2020
  • 13 Min read
  • 1,143 Views
Web Development
React

Introduction

Any of us might receive a request from a client who has a custom API that produces massive amounts of data. The client may request an interactive visualization that will facilitate their users' decision-making process. It can feel like a "Mission Impossible" with the deadline lurking close. On a positive note, the client is easy on the specifications and no restrictions are placed on the technology as long as it gets the job done.

We would probably immediately decide to go with React +d3.js, a tiny, ideal JavaScript library.

However, under certain circumstances, this may not work. If the client is old-fashioned, they may have a web app designed using Joomla or WordPress, and jQuery constituted the front-end stack. The entire app may have been written using these framework plugin components, leaving the poor server alone to handle the back end.

The client’s team of engineers is not going to waste time shifting their project to React so we can create one simple component. We know the answer will be "no" to that, and we have to commit to their rules.

Solution

When all is said and done, it is a mere website, which means we will be dealing with a web browser, HTML, CSS, the DOM model, and so forth.

Even if we do not find ES6 or a build system, and even if we don't have the JavaScript module management or state-of-the-art features we are accustomed to, you can export the entire visualization component into a global function and thus call it anywhere in the code, as shown below:

1
2
3
4
5
6
7
8
9
10
11
const React = require('react'),
           Chart = require('./chart');

function RenderChart(options) {
    React.render(
        <Counter />,
        document.querySelectorAll(options.selector)[0]
    );
}

window.RenderChart = RenderChart;
javascript

The client can now integrate the React component by loading React, then the compiled JS file. and finally calling the said function using specified arguments. One problem down.

The overhead caused by loading the entire React library for one tiny component is a small price to pay. (React library is only 38KB after minification and compression, barely 10KB more than jQuery. )

Even better, if a public CDN is employed, plenty of users will already have React cached on their devices. This means we will need a less-than-1KB download to make sure they have the right version.

The client is happy, but we are not.

Global functions are really not the perfect approach to integrate a React component. Global functions might ruin the global namespace, and their communication is one-way, one-time. Moreover, after we invoke our component, it is free in the wild and we cannot keep track of what is going on with it.

Using React Components as jQuery Plugins

We can easily use React components as jQuery plugins. We can create Backbone views and Angular components. This may offend the reasonable, well known architecture we have worked with our entire lives, but face it: it’s the perfect solution.

There is no website or a web application born in the last decade that lacks jQuery. No sane web developer would avoid using any jQuery plugin at all costs.

To really answer our earlier question, look at the following:

1
<div class="counter" style="border: 1px solid #eee; padding: 2em;"><div data-reactid=".0"><p data-reactid=".0.0"><span data-reactid=".0.0.0">You've clicked Our Button </span><span data-reactid=".0.0.1">7</span><span data-reactid=".0.0.2"> times!!</span></p><button class="btn btn-primary" data-reactid=".0.1">Our Button</button></div></div>
html

This is only a component that counts how many times "Our Button" was clicked—no rocket science.

Other than that, we have jQuery to perform rendering and two buttons to work with the said component after rendering.

The thing about jQuery-driven white buttons is that they have the ability to read and write the component’s internal state, and they do not require React in order to do so. They look like any jQuery code we've worked with before.

Using the Component/Plugin

To render our component, we will need this simple line of code:

1
$(".container .counter").clickCount();
javascript

So we just select the element and call the required plugin. The plugin will reveal its internal state by means of the val() function, which we will see shortly. However, we can use it like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// src/integrate.js
$(".btn-20x").click(function () {
    var counter = $(".container .counter")
            .clickCount()[0];

    counter.val(counter.val()+10);

});

$(".btn-get").click(function () {
    var val = $(".container .counter")
            .clickCount()[0]
            .val();

    alert("Current counter value is: "+val);

});
javascript

It is a matter of selecting the designated element, getting its instance of the plugin, and then calling the function val() we mentioned earlier. This can be done with or without parameters, depending on what we have in mind for the code.

We need to use .clickCount()[0] because we do not have the means to turn the plugin into a per-element singleton. We can get the reference easily. However, jQuery selectors can return arrays, so we, too, will have to return an array. In order to make the life of the .val() function easier and avoid confusing it, we have to access a single element of our returned array.

And now we have done it. We have a React component that anyone who has knowledge of jQuery can use without React itself. What sorcery is this?

Packing React Components into jQuery Plugins

In order to transform our React component into a jQuery plugin, we need to do two things. First, we obviously need to create our plugin. Second, we need to compile our code so that it works well on everyday devices.

Let’s take it slowly and start with the plugin.

With src/main.jsx, we hit two birds with one stone as it behaves as the component’s entry point and at the same time exports all data required beyond. Now, when someone wishes to use this component from React, they only need to require it directly.

So, we load React and our component. Now it’s time to create a rendering function:

1
2
3
4
5
6
7
8
9
10
// src/main.jsx
const React = require('react'),
      Counter = require('./Counter');

function RenderCounter(selector) {
    React.render(
        <Counter />,
        document.querySelectorAll(selector)[0]
    );
}
javascript

This code snippet enables us to not only export the function, but also to assign it as global, as follows:

1
2
module.exports = RenderCounter;
window.RenderCounter = RenderCounter;
javascript

So, if another developer who does not use React but has a dependency loader wants to use this component, they can require it and then use the function to render it within any element of choice.

Moreover, if they do not have a dependency loader, they can use the global function.

The jQuery boilerplate project actually provided the foundation for this jQuery plugin.

Below is how our main.jsx would look:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// src/main.jsx
if (typeof jQuery !== 'undefined') {
    (function ($) {
        var pluginName = "clickCount",
            defaults = {
                value: 0
            };

        function Plugin(element, options) {
            this.element = element;
            this.settings = $.extend({}, defaults, options);
            this._defaults = defaults;
            this._name = pluginName;
            this.init();
        }
     
        $.extend(Plugin.prototype, {
            init: function () {
                this.component = React.render(
                    <Counter value={this.settings.value} />,
                    this.element
                );
                return this;
            },
     
            val: function (val) {
                if (!arguments.length) {
                    return this.component.state.counter;
                }else{
                    this.settings.value = val;
                    this.init();
                }
            }
        });
    })(jQuery);

}
javascript

We only produce a plugin if jQuery is accessible. The plugin’s definition is wrapped in a closure in order for the pluginName and defaults variables to be shared.

Next, a constructor function called Plugin is defined to hold the basic properties of the recently constructed object and call init.

We can render our React component within init. However, since this.element already refers to the target DOM element, we do not need to use document.querySelectorAll.

The val function is used to access the internal state of the component. If a parameter is passed, we change the value and perform a re-rendering of the component. React can handle this by updating the component with the new properties.

However, if no parameters are passed to val, it will simply return the current state of the component.

Notice how we accessed the internal state of the component using this.component.state.counter. We just need to be aware of where the components’ states are stored. There is no real React incantation in there.

The next few lines of code are necessary now to add the plugin to jQuery:

1
2
3
4
5
6
7
8
9
10
// src/main.jsx
        $.fn[pluginName] = function (options) {
            return this.map(function () {
                if (!$.data(this, 'plugin_'+pluginName)) {
                    $.data(this, 'plugin_'+pluginName, new Plugin(this, options));
                }
                return $.data(this, 'plugin_'+pluginName);
            });
        };
    })(jQuery);
javascript

Now we can call $(...).clickCount(). Considering that each instance of the plugin is stored in each element’s $.data collection, we can guarantee that each instance is handled as a per-element singleton. This way, whenever clickCount is called on an element, the same object is returned. We might find this to be convenient should we wish to manipulate the internal values of the components.

Webpack: Compiling React for the Real World

We have the plugin ready, but what good does it serve if others can not use it? To fix this, we need to create a JavaScript file that works for others as well.

To achieve this, we can use Webpack. It is a simple tool that works as a JavaScript dependency manager just like RequireJS or Browserify.

Webpack works as follows: it analyzes ourrequire() statements in order to create a dependency tree of the code. It next produces a single JavaScript file that contains all that our code will later require.

Webpack might divide our code into various multiple modules, load stylesheets, JavaScript templating files, and even images.

To avoid the details, just employ React to turn ES6/JSX code into the usual JavaScript and compile a single file that the end user can load.

It sounds harder than it actually is. We only have to declare React and jQuery as externals to Webpack and place the compiled code in a ./build/counter.js file. Config.js will be as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// webpack.config.js
module.exports = {
    entry: [
        './src/main.jsx'
    ],
    output: {
        filename: 'counter.js',
        path: path.join(__dirname, 'build'),
        publicPath: '/build/'
    },
    module: {
        loaders: [
            {
                test: /\.jsx$/,
                loaders: ['babel'],
                include: path.join(__dirname, 'src')
            }
        ]
    },
    resolve: {
        extensions: ['', '.js', '.jsx']
    },
    externals: {
        "react": "React",
        "react/addons": "React",
        "jQuery": "jQuery"
    },
};
javascript

Now Webpack knows that ./src/main.jsx is our module’s entry point and that’s where the plugin code is.

Next, we need to tell Webpack where it will place the code after compilation—./build/counter.js in this case. babel-loader is used for source files.

Now we can use React’s JSX syntax to easily embed HTML in JavaScript, not to forget the sophisticated features of ES6.

Webpack will not attempt to bundle React and jQuery into the compiled file as we told it that they are external dependencies using externals.

We might actually wish sometimes to bundle React and jQuery into the same compiled file. However, that would sacrifice the advantages of utilizing public CDNs and the end user’s browser cache.

Conclusion

We now have our React component packaged as a jQuery plugin. This is especially useful when we can not carry out an exhaustive code rewrite due to time constraints or budgetary limits.

We only need to write the component, stash it in a thin jQuery wrapper, and ensure that the real world can include our file and make use of it.

5