Using React Components in Kinetic Forms

Anne Ramey
UI Developers
Understanding of REACT

Kinetic Request CE bundles are created using React which utilizes ES6 javascript. Kinetic Request CE Forms allow plain javascript or jQuery to be used. These two types of javascript are not interchangeable nor work together easily. React requires a “store” to hold and manipulate data and the code is loaded when the first page is loaded, then, components are added as they are used in the portal. jQuery or plain javascript is loaded/reloaded at each page change and interpolated at runtime. This is where we run into the issue of how to maintain consistency between the form content, layout, and actions, and the bundle (or portal) content, layout and actions since they cannot talk to each other easily.

Since Kinetic bundles began we have maintained a namespace, “BUNDLE” in Kinetic RE and “bundle” in Kinetic CE for configuration variables, javascript functions, and widgets that could be used bundle wide. This namespace is also available to the forms which allows us to bridge the gap between React bundle code and Javascript form code.

Setting up your link

We need to expose the components from the React bundle before we can try to use them in the form. This is where we insert a component into the bundle object using React render. This allows React to keep control and maintain the state of the component (all the logic that you would write in javascript) and just pass the “interface” to the form. We can manipulate how the component displays and the data the component with the component attributes.

Inside your React code in the app package under lib/bundle/helpers you can either add to the index.js file or create your own file. You will import your component and then add code that leverages the render function from the react-dom package. See the simple example below:

import React from 'react';
import ReactDOM from 'react-dom';
import { bundle } from 'react-kinetic-core';
import { Alert } from 'reactstrap';

bundle.helpers = {
  alert: (element, { type, message }) => {
    ReactDOM.render(<Alert color={type}>{message}</Alert>, element);
  }
};

In this example we have included the react-dom library and are using the render from that to output the Alert component from reactstrap configured with attributes that manage color and a dynamic message. We have added this component to the bundle object under “helpers”. We are passing the element it will be added to (this will be an element from the form), a “type” which is used in the color attribute, and the alert message we want displayed.

Adding the component to your form

Now that the component is in a function in the bundle object we can call it from the form using the bundle namespace:

bundle.helpers.alert(
  K('form').find('#placeholder')[0],
  {
     type: 'success',
     message: 'You succeed!'
  }
);

Note: We use K('form').find(...)which is a wrapper of jQuery's find function but it has some special logic in it so that it will not be tricked by subforms (guarantees that matches only belong to the specific form itself, not subforms).

So the above code is passing the “placeholder” element for the component to use for it’s display. Everything else is managed in the React code.

Advanced example

The above simple example takes in data and renders the component. In the typical React dataflow, props are the only way that parent components interact with their children. To modify a child, you re-render it with new props. However, there are a few cases where you need to modify a child outside of the typical dataflow. The child to be modified could be an instance of a React component, or it could be a DOM element. For both of these cases, React provides a way to create a reference (ref).

Refs are created using React.createRef() and attached to React elements via the ref attribute. Refs are commonly assigned to an instance property when a component is constructed so they can be referenced throughout the component. There are a few good use cases for refs:

  • Managing focus, text selection, or media playback.
  • Triggering imperative animations.
  • Integrating with third-party DOM libraries. Avoid using refs for anything that can be done declaratively.

For more in depth information regarding refs see: https://reactjs.org/docs/refs-and-the-dom.html

For this advanced example we will use a third-party library for creating digital signatures called react-signature-canvas which allows us to capture a digital signature. Unlike the alert, the signature widget needs to provide the Kinetic form events a way to interact with the resulting SignatureCanvas React component. For example, we may want a way to clear the signature and we need to be able to sync the signature data with a form field for storage.

To use the library effectively we will need to do the following actions:

Load an already saved signature from a draft or review submission when the component mounts. Update the signature data when the user modifies or adds a signature.

To accomplish this we will be wrapping the library to include the functions that the form events can use. In the example below we’ve effectively created a new component called SignatureCanvasWrapper where we’ve exposed the setValue, componentDidMount, and onEnd functions. We can then call the setValue function to update the signature data in our forms.

import React from 'react';
import ReactDOM from 'react-dom';
import { bundle } from 'react-kinetic-core';
import SignatureCanvas from 'react-signature-canvas';

class SignatureCanvasWrapper extends React.Component {
  constructor(props) {
    super(props);
    this.onEnd = this.onEnd.bind(this);
  }

  setValue(value) {
    const { height, width } = this.props;
    if (value) {
      this.signatureCanvas.fromDataURL(value, { height, width });
    } else {
      this.signatureCanvas.clear();
    }
  }

  componentDidMount() {
    const { initialValue, height, width } = this.props;
    if (initialValue) {
      this.signatureCanvas.fromDataURL(initialValue, { height, width });
    }
  }

  onEnd() {
    const { onChange } = this.props;
    if (typeof onChange === 'function') {
      onChange(this.signatureCanvas.toDataURL());
    }
  }

  render() {
    const { height, width } = this.props;
    return (
      <SignatureCanvas
        canvasProps={{ height, width }}
        onEnd={this.onEnd}
        ref={el => (this.signatureCanvas = el)}
      />
    );
  }
}

Again, we will need to link our React component with our bundle object so that it is available to the Kinetic form. Here you can see we are adding signatureCanvas to our bundle object under “helpers” and it is rendering our wrapped library SignatureCanvasWrapper. We are passing the element we want to render the component, initialValue (the signature data if it exists from the submission), a height and width for the rendering of the component and an onChange function.

bundle.helpers = {
  signatureCanvas: ({ element, initialValue, height, width, ref, onChange }) => {
    ReactDOM.render(
      <SignatureCanvasWrapper
        initialValue={initialValue}
        onChange={onChange}
        ref={ref}
        height={height}
        width={width}
      />,
      element
    );
  }
};

Next we will use form actions to load and interact with our signature component. The code below is invoked on a page load event:

bundle.config.widgets.signatureCanvas({
  element: K("form").find("#signature")[0],
  initialValue: K("field[Signature Value]").value(),
  height: 250,
  width: 500,
  ref: function(el) {
    window.signaturePad = el;
  },
  onChange: function(value) {
    K("field[Signature Value]").value(value);
  }
});

This code passes a ref function that stores the element globally so that it is available to all form events. We also pass an onChange callback that stores the signature data in a specified field.

Currently we do not have a better way to store things like these elements, but if it does not need to be accessed by other events we should store it as a local variable in this event code.

Finally, we add an onChange event to the form field that stores the signature data. This event uses the element instance to update the signature if the field value is changed:

if (window.signaturePad) {
  window.signaturePad.setValue(K("field[Signature Value]").value());
}

Now we can implement a clear button by just setting this field's value to null, it does not need to include any code that is specific to this signature component's implementation.