Dealing with legacy code in React

Jack Franklin
10 May, 2018

At Thread we’ve a large, five year old codebase that has lived through several frameworks, libraries and techniques, particularly on the frontend. These days we’re primarily writing our frontend in React, using CSS Modules. In contrast, most of our old frontend code is jQuery layered on top of server side rendered content. This means we spend a reasonable amount of time converting jQuery into React components.

The CollectPlus widget

On a recent project we were rebuilding part of our checkout flow in React. The first step of our checkout is to pick your shipping address, and one option that we offer is CollectPlus. The checkout page was written primarily in jQuery and used a CollectPlus widget library to load the CollectPlus UI and allow the user to pick the store for their parcel to be sent to. The code was pretty standard jQuery that would look for a div on a page that has a certain set of attributes that we need (such as the CollectPlus API key), and initialise the CollectPlus widget via the parcelpoint API library that we use.

function setup_click_and_collect() {
var api_key = $collect_plus_widget.data('api-key');
// parcelpoint is the provided collect plus widget
parcelpoint.Store.init({
apiKey: api_key,
country: {
name: 'United Kingdom',
code: 'uk',
},
targetDiv: $collect_plus_widget,
// more options here, left out
});
parcelpoint.Store.display(display_opts);
parcelpoint.Property.onChange(['storeDetails'], function(_, newDetails) {
// deal with the user picking a new store
});
}
view raw initial-jquery.js hosted with ❤ by GitHub

We wanted to turn our CollectPlus code into a React component so it could fit into the rest of our new checkout , but one thing that we didn’t want to do is rework the old jQuery code; the CollectPlus widget (that is provided by a 3rd party for us) functioned perfectly, and it wasn’t worth our time to completely rebuild it from scratch, given that we could take advantage of a 3rd party library that provided the functionality for us.

To avoid rebuilding, we were able to take advantage of React’s references features to wrap this legacy CollectPlus code in a React container.

The CollectPlus widget takes a selection of configuration options and a DOM reference, into which it will mount the CollectPlus widget. Typically when working with React you don’t want to reference the DOM; you should give React a selection of components and allow it to do the messy work with the DOM, but React provides an escape hatch when you need it in the form of references. Using references allowed us to move forward with the checkout rewrite without being delayed on rewriting the CollectPlus widget code entirely in React.

References in React

React uses references to provide a direct link to the DOM node that was rendered. You access this via a callback ref prop that you pass to an element within your component's render function:

render() {
return <div ref={div => this.divRef = div} />
}
view raw refs-in-react.js hosted with ❤ by GitHub

Please be aware that in the React 16.3 there is a new Ref.createRef() API that it is recommended you use; but when we first wrote this code we weren’t on the latest React, so these examples use the older callback ref API, which is still implemented in React.

When this render function runs, React will call the ref property and set this.divRef to have a reference to the actual div that was rendered into the DOM. By the time the componentDidMount lifecycle function fires, we can be confident that this.divRef has been set, and we can trigger any code that relies on the presence of this element in the DOM.

Running the CollectPlus widget within React

The first step was to take our legacy code and wrap it in a function that can take the div element and the CollectPlus API key:

function setupClickAndCollect(divToRenderIn, apiKey) {
parcelpoint.Store.init({
apiKey: apiKey,
country: {
name: 'United Kingdom',
code: 'uk',
},
targetDiv: divToRenderIn,
// more options here, left out
});
// code omitted to save space
}
view raw wrapped-legacy.js hosted with ❤ by GitHub

Now we can create a CollectPlus component that can call the above function, passing in a reference from React:

class CollectPlus extends Component {
componentDidMount() {
setupClickAndCollect(this.collectPlusContainerRef, this.props.apiKey)
}
render() {
return <div ref={div => this.collectPlusContainerRef = div} />
}
}
view raw collect-plus.js hosted with ❤ by GitHub

Dealing with user events

Our final task is to deal with the callback that occurs when the user interacts with the widget and choses the store that they would like to use.

We can update our setupClickAndCollect code to also take in a callback function and implement it on the React side:

function setupClickAndCollect(divToRenderIn, apiKey, onStoreChange) {
// code omitted to save space
parcelpoint.Property.onChange(['storeDetails'], function(_, newDetails) {
onStoreChange(newDetails)
});
}
view raw event-handler.js hosted with ❤ by GitHub

We can define the event handler in the React CollectPlus component. This has the advantage that we can deal with the state (which here is the store the user has picked) in React land, and only use our legacy jQuery for the actual event handler:

class CollectPlus extends Component {
componentDidMount() {
setupClickAndCollect(this.collectPlusContainerRef, this.props.apiKey, newStore => {
this.setState({ selectedStore: newStore })
})
}
}
view raw final-component.js hosted with ❤ by GitHub

Conclusion

One of the benefits of React components is that they are all isolated; each one has its own set of responsibilities that it deals with, and as a user you can treat them as a black box. You can take advantage of this characteristic when dealing with legacy code; this approach can be used to take some legacy code and wrap it in a more modern surrounding that allows you to move forward without refactoring every single last piece of code.


If you’ve enjoyed this blog post, you might be interested to know that we are hiring! Check out our frontend engineer job listing to find out more about the role and how to apply.

Dealing with legacy code in React

Jack Franklin
10 May, 2018