TIBET Logo

TIBET + ReactJS

React

wins

  • Leverage existing investment in ReactJS widgetry.
  • Fully integrates with TIBET's data binding system.
  • Together with the Sherpa, allows on-the-fly authoring of JSX.

contents

concepts

cookbook

code


concepts

TIBET is a fully self-contained authoring system that provides a full stack environment. Many organizations, however, already have made a significant investment in ReactJS widgetry. TIBET provides a 'bridge' that can connect TIBET's custom tag environment and data binding system to ReactJS widgetry, allowing that investment to be preserved.

ReactJS-based TIBET tags can participate as peers with other TIBET-only technologies, such as XControls, in a TIBET application. This provides a mix-and-match environment.

TIBET does this by leveraging two of its key technologies:

  1. The UI canvas frame. All of TIBET's drawing is done in an <iframe> that no TIBET code is loaded into. Only the two TIBET globals, TP and APP are available in the UI canvas frame. This allows ReactJS to be loaded into the UI canvas frame easily without worrying about conflicting with TIBET code.

  2. TIBET supplies a special TIBET type that gets 'mixed in' to a custom tag type that the component author is building to 'wrap' the ReactJS widget. This mixin process uses TIBET's multiple inheritance type system (to those familiar with multiple inheritance, TIBET uses a combination of the C3 linearization algorithm and manual overrides to avoid the "Deadly Diamond of Death" problem :-) ).


cookbook

Building a ReactJS-backed TIBET custom tag (no JSX).

NOTE: The following code is taken from the ReactJS demos located in the top-level 'demo' directory of the TIBET library.

Assume a ReactJS component defined like this:

'use strict';

/* eslint-disable class-methods-use-this,no-console */
class LikeButton extends React.Component {
    constructor(props) {
        super(props);
        this.state = {liked: false};
    }

    render() {
        if (this.state.liked) {
            return 'You liked this.';
        }

        return React.createElement(
          'button', {
              onClick: () => {
                  this.setState({liked: true});
              }
          },
          'Like'
        );
    }
}

//  NOTE: THIS IS REQUIRED FOR TIBET TO FIND THIS CLASS
window.LikeButton = LikeButton;

Create a subtype for your custom tag. This might be TP.tag.CustomTag, but it might be a subtype of TP.tag.CustomTag if you're building out a hierarchy of your own. This is the same operation you would do even if this weren't a ReactJS-based custom tag:

TP.tag.CustomTag.defineSubtype('APP.mytagset.mylikebutton');

Then, to 'ReactJS enable' your tag, you need to 'mix in' traits from TP.dom.ReactElement. This uses TIBET's multiple inheritance system:

APP.mytagset.mylikebutton.addTraits(TP.dom.ReactElement);

Provide one type-level method to tell TIBET where to find the ReactJS script that is the definition of your component:

APP.mytagset.mylikebutton.Type.defineMethod('getComponentDefinitionLocations',
function() {

    /**
     * @method getComponentDefinitionLocations
     * @summary Returns an Array of URL locations that contain JavaScript code
     *     used to define the React component that the receiver is representing.
     * @returns {String[]} An Array of script URL locations containing the React
     *     component definition code.
     */

    //  this.qualifyToPath() computes a path that to where this TIBET type was
    //  loaded from. The system will expect to find 'like_button.js' in the
    //  same directory as this type definition.
    return TP.ac(this.qualifyToSourcePath('like_button.js'));
});

Provide one instance-level method to tell TIBET what constructor (i.e. 'class') name to use when constructing the component:

APP.mytagset.mylikebutton.Inst.defineMethod('getComponentClassName',
function() {

    /**
     * @method getComponentClassName
     * @summary Returns the React component's ECMAScript class name. This is
     *     used by the TIBET machinery when invoking the ReactDOM.render call.
     * @returns {String} The React component's ECMAScript class name.
     */

    return 'LikeButton';
});

Then, just use your custom tag how you would any other TIBET tag in the system:

<mytagset:mylikebutton/>

Building a ReactJS-backed TIBET custom tag (JSX).

Assume a ReactJS component defined like this, using JSX:

class Greeting extends React.Component {
    render() {
        return (<p>Hello there Bill!</p>);
    }
}
ReactDOM.render(
    <Greeting />,
    document.getElementById('root')
);

Create a subtype for your custom tag in exactly the same way you did with a non-JSX component:

TP.tag.CustomTag.defineSubtype('APP.mytagset.mygreeting');

Again, 'mix in' traits from TP.dom.ReactElement.

APP.mytagset.mygreeting.addTraits(TP.dom.ReactElement);

This time, because both the component definition and the creation are in one file, we provide one instance-level method to tell TIBET where to find the ReactJS script that is both the definition and creation of your component. This time, this will point to a JSX file.

APP.mytagset.mygreeting.Inst.defineMethod('getComponentCreationLocation',
function() {

    /**
     * @method getComponentCreationLocation
     * @summary Returns a location containing the ReactJS script that will
     *     create the component. The resource pointed to by this URL may contain
     *     regular JavaScript or JSX.
     * @returns {String|null} The component creation location.
     */

    //  Note that, unlike getComponentDefinitionLocations(), we do not return an
    //  Array here.
    return this.qualifyToSourcePath('react_greeting.jsx');
});

Last, as before with the non-JSX tag, we provide one instance-level method to tell TIBET what constructor (i.e. 'class') name to use when constructing the component:

APP.mytagset.mygreeting.Inst.defineMethod('getComponentClassName',
function() {

    /**
     * @method getComponentClassName
     * @summary Returns the React component's ECMAScript class name. This is
     *     used by the TIBET machinery when invoking the ReactDOM.render call.
     * @returns {String} The React component's ECMAScript class name.
     */

    return 'Greeting';
});

Again, just use your custom tag how you would any other TIBET tag in the system:

<mytagset:mygreeting/>

Supplying a ReactJS-backed TIBET custom tag with props.

There are two mechanisms that can be used to supply props to a ReactJS-backed TIBET component:

  1. Use attributes on the tag instance in the markup
<mytagset:myreactwidget foo="bar" baz="goo"/>

In this case, two String props, foo and baz, with values of "bar" and "goo" will be supplied to the widget upon creation.

Since this is done when the tag is instantiated, any values that you assign to foo and baz after the tag is instantiated (i.e. after TIBET's "tag attachment" cycle) will not be considered by the ReactJS component.

Also only String props will be created using this mechanism.

  1. Implement the getNonAttributeProps instance method on your custom tag type:
APP.mytagset.myreactwidget.Inst.defineMethod('getNonAttributeProps',
function() {

    /**
     * @method getNonAttributeProps
     * @summary Returns a hash containing props that should be supplied to the
     *     React component that are not represented as element attributes. These
     *     will be added to the element's attributes and then supplied to React
     *     when the element is created.
     * @returns {TP.lang.Hash} A hash containing values that will be supplied to
     *     the React component upon instantiation.
     */

    return TP.hc('age', 42);
});

This will supply a prop of "age" and a Number value of 42. This is the way to provide non-String props.

Note that the props returned in the hash from getNonAttributeProps will be combined with the (String-only) props gathered up as attributes on the tag as shown above.


code

The TP.dom.ReactElement type is defined in ~lib/src/react/TP.dom.ReactElement.js.