Sometimes your React components are wrappers for external components. And those external components APIs might not be declarative. Instead, they provide you with a set of imperative APIs that you can use to change their state. What can you do in such situation?

I recently stumbled upon this problem when integrating our React frontend with seats.io library. The library renders venue map and provides you with callbacks when certain seats are selected or deselected. It also updates the venue map via WebSockets. When seats are taken or released by other users our map reflects it.

Seats.io Venue Map

I’ve been happy with the results. But like I said, their API is imperative. It has methods such as:

  • clearSelection() - Unselect currently selected seats
  • setUnavailableCategories(list) - Takes an array of category keys (e.g. ["VIP", "Left sector"]`). Use this if you want to disable categories, e.g. per user role, a moment in time, and whatnot.

And similar…

Mounting is easy

import { isEqual } from 'lodash';
import React, { Component } from 'react';

const DIV_ID = 'venue_map';

class VenueMap extends Component {
  componentDidMount() {
    this.seatingChart = new seatsio.SeatingChart({
      divId:                 DIV_ID,
      publicKey:             this.props.publicKey,
      event:                 this.props.eventKey,
      onObjectSelected:      this.props.seatSelectedHandler,
      onObjectDeselected:    this.props.seatDeselectedHandler,
      unavailableCategories: this.props.unavailableCategories,
      selectedObjects:       this.props.selectedObjects,
    });
    this.seatingChart.render();
  }

  render() {
    return <div id={DIV_ID}></div>;
  }
}

As you can see providing a list of selected seats and unavailable categories is easy when the venue map is initially rendered. We can use unavailableCategories and selectedObjects config options. But there are use-cases where these two change in run time.

When a sale is finished and POS system is ready to handle the new order, we want a clear list of selected seats. We should call clearSelection().

Or when the user is a VIP and knows a secret that unlocks part of the venue which is unavailable. We should use setUnavailableCategories() to unlock it.

componentWillReceiveProps(object nextProps) to the rescue

componentWillReceiveProps - Invoked when a component is receiving new props. This method is not called for the initial render. Use this as an opportunity to react to a prop transition before render() is called.

  componentWillReceiveProps(nextProps) {
    if (nextProps.bookingId !== this.props.bookingId){
      this.seatingChart.clearSelection();
    }
    if (!isEqual(nextProps.unavailableCategories, this.props.unavailableCategories)){
      this.seatingChart.setUnavailableCategories(nextProps.unavailableCategories);
    }
  }

componentWillReceiveProps lifecycle method gives you an access to current properties via this.props. You also have an access to properties which are going to be used in a moment during the next render via nextProps. This gives us the ability to compare both of them and react. For example by calling an appropriate imperative API.

In our case, we check if bookingId didn’t change. It’s an identifier for booking the places selected on the VenueMap. If it changed, it means we are processing a new order and the seats need to be cleared.

And for the categories, we compare unavailableCategories. If it changed we call setUnavailableCategories API. Or, if there is no negative performance implication, we can always call the method.

  componentWillReceiveProps(nextProps) {
    if (nextProps.bookingId !== this.props.bookingId){
      this.seatingChart.clearSelection();
    }
    this.seatingChart.setUnavailableCategories(nextProps.unavailableCategories);
  }

It really depends on the library you are integrating with, and the specific use-case you have in your app.

Imperative programming imposed

Once you have to deal with an imperative API there is no easy to avoid it. It must affect your code in some places. However, you can keep it mostly hidden from your declarative API.

Sometimes you don’t have a choice and you need to cooperative with given library. Like in my case of seats.io integration that we have. But sometimes you can do yourself a favor and look for a library which is compatible with React way.

A few days ago I refactored a React wrapper for a modal library with an imperative API. I moved to react-modal and I was satisfied with the number of lines removed.

Remember to clean up

If you read The real reason to avoid jQuery then you know it. Appplications’ performance often suffers because of libraries which don’t clean up after themselves. So be a good developer and remember to use componentWillUnmount to clean up the integration.

comments powered by Disqus