There are plenty of articles talking about React, jQuery. Some cover the reasons why you should (and you shouldn’t) integrate codebases using both JavaScript libraries. You may even find some encouraging you to integrate React into your legacy sites, as a first step to migrate your existing codebase to a more modern web development toolset.

On the other hand, there are other authors trying to warn you about all the caveats of a hybrid solution, and try o push you towards rewriting the legacy application, at least at component level:


For whatever reason you’ve determined it’s not practical to rewrite your jQuery code. Here is an important caveat: I think using jQuery and React to manage updates to the same DOM elements is a bad idea.  […] So unless you can cleanly separate the DOM elements in your UI so that some only get updated by React and others only get updated by jQuery, I wouldn’t try it.

Can’t avoid scenario

But sometimes you can’t simply avoid it. At Vistaprint, we have a UI library that manages all HTML components shown in our website, similar to our internal bootstrap. From buttons, forms, links, … to complex carousel or slider components live in a centralized package. That helps us to ensure consistency in terms of styling and behavior.

Current UI Library uses jQuery to tweak a few things. And while we’re in the process of creating a new react-based UI Library, it’s not 100% ready yet.

And there are chances that, in the meantime, we have to live in a world where we’re using both UI Library jQuery elements and React components in the same application. Sometimes, that is extremely challenging.

Ideal code

We are using Typescript to write our React apps. By working with a strongly typed programming language, we avoid some errors when coding, and it brings us compile-time checks.

Here you have some sample code, defining a component that changes a title when a checkbox is checked.

import * as React from 'react';

class MyComponent extends React.Component {

  private title: string;

  constructor(props: any) {
    super(props);
    this.title = '';
  }

  handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    const target = event.target;
    const value = target.checked;
    
    // we would propagate this to the state
    // but for simplicity, we'll leave it here
    this.title = `A new title: ${ value }`;
  }
  
  render() {
    return (
        <div>
            <span>{this.title}</span>
            <input
                type="text"
                onChange={this.handleInputChange}
            />
        </div>
    );
  }
}

Our issue: jQuery triggered events

A very important problem we may face is if we’re using jQuery change, click,… events. They are shortands for trigger('change') or trigger('click')calls, and as documented in the jQuery documetation, that doesn’t trigger real events. Here you have the relevant paragraphs:


A call to .trigger() executes the handlers in the same order they would be if the event were triggered naturally by the user .

[…]

Although .trigger() simulates an event activation, complete with a synthesized event object, it does not perfectly replicate a naturally-occurring event.

And this is what was happening to us: in another part of our codebase, some jQuery-based scripts where changing the input and then doing a $(elem).trigger('change'). Those jQuery-triggered change events were not being captured by React onChange listeners.

Workaround

Disclaimer

Only do this if you really need it. The code needed for the workaround to work is fragile, and you should try by all means to get to a place where you only use React to handle the whole component.

In order to workaround the issue, we needed to jQuery-capture the event, and then throw it as a regular event.

// jquery-events.js

function propagateChangeEvent(elem, callback) {
  $(elem).on('change', function (e) {
    callback(e);
  })
}

export { propagateChangeEvent };
import * as React from 'react';
import { propagateChangeEvent } from '../jquery-events';

class MyComponent extends React.Component {

  private elem: any | null = null;
  private title: string;

  constructor(props: any) {
    super(props);
    this.title = '';
  }

  componentDidMount() {
    // this line does the magic: it connects the input element with 
    // the method we want to trigger when a change is detected
    propagateChangeEvent(this.elem, this.handleInputChange);
  }

  handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    const target = event.target;
    const value = target.checked;
    
    // we would propagate this to the state
    // but for simplicity, we'll leave it here
    this.title = `A new title: ${ value }`;
  }
  
  render() {
    return (
        <div>
            <span>{this.title}</span>
            <input
                type="text" 
                ref={ el => this.elem = el }
                // onChange={this.handleInputChange} we don't need this anymore
            />
        </div>
    );
  }
}

Conclusion

Our recommendation: you should only do this mixed approach if you’re planning to move to a React-only rendering. In the transition period, you may want to assume the overhead of working with both libraries together.

But if you don’t have plans to completely remove jQuery for your React components, you should probably reconsider and not try to mix both libraries.


0 Comments

Leave a Reply

Your email address will not be published. Required fields are marked *