tao.js Adapter for React

This is a description for part of the first ("original") API for integrating the TAO with React. For a description of the Current API which provides a more React-like declarative approach, take a look here.

To first integrate the TAO with the programming interface provided by React, we use the tao.js Adapter from the @tao.js/react package.

Similar model to Official Context API

React 16.3.0 introduced the Official Context API and with it came the Provider and Consumer binary component relationship that we can use to manage context for our Components in our React Apps.

The relationship between the Adapter and Reactor is very similar to the Context API components in that just like the Provider is providing values for the Consumer to consume and use, the Adapter is providing React Components for the Reactor to render into the UI. You could say that Adapter and Reactor are the yin & yang ☯ of @tao.js/react (couldn't resist).

One of the stark differences to notice however is that the Adapter itself is not a Component, but it does provide it's attached Components to the Reactor.

Adapting React Components to be Handlers

The purpose of the Adapter is to convert our React Components into handlers for the TAO. Because React Components are functions in JavaScript, we can take advantage of this to make this adaptation easy.

The Adapter will work on any React Component, however we will generally design our Components to take advantage of the props passed to them as adapted from the tao and data args passed to handlers by the TAO when an Application Context is set.

Fluent Methods

The Adapter provides fluent method chaining for all of the public methods that we use on the Adapter by returning a reference to the Adapter itself from these methods. For those unfamiliar, fluent method chaining allows us to make a sequence of calls to the Adapter in a single statement, like:

adapter
  .setDefaultCtx({ t: 'Space', o: 'Portal' })
  .addComponentHandler({ a: 'View' }, View)
  .addComponentHandler({ a: 'List' }, List);

Importing Adapter

First, before using the Adapter it must be imported along with the TAO which is the argument passed to the Adapter's constructor:

import TAO from '@tao.js/core';
import { Adapter } from '@tao.js/react';

OR

const TAO = require('@tao.js/core');
const { Adapter } = require('@tao.js/react');

Using a Component as a Handler

Using a Component as a handler for an Application Context is easy.

Instead of calling the regular add[Type]Handler passing a handler function like:

TAO.addInlineHandler({ t: 'Space', a: 'Enter', o: 'Portal' }, (tao, data) => {
  // do something here
});

We create an Adapter and use the addComponentHandler method passing along our React Component like:

import MyComponent from './MyComponent';

const adapter = new Adapter(TAO);
adapter.addComponentHandler({ t: 'Space', a: 'View', o: 'Portal' }, MyComponent);

When we do this, the Adapter adds an Inline Handler to the TAO that will be called when the {Space,View,Portal} Application Context is set on the TAO.

When this occurs (the AppCon is set to what we're listening for), the Adapter will make the Component its current component for rendering by the Reactor which is itself a Component that will handle rendering our Component as its child.

tao and data handler args combined as props

When the adapted handler receives the call from the TAO, it keeps track of the tao and data args passed to it so they can be included as props for our instantiated Component.

However, since props is a single arg passed to the constructor of our Component, they have to be combined. Although technically it's the Reactor that is managing this, it's useful for the explanation here in order to understand how other options to the addComponentHandler and other methods on the Adapter will instantiate our Components to help us in designing and creating our Components for use in this way.

The tao and data objects are merged to create the props on our Component so that we at least end up with the following props passed to our Component:

  • t: string - the t from the tao trigram arg represents the Term
  • a: string - the a from the tao trigram arg represents the Action
  • o: string - the o from the tao trigram arg represents the Orient(ation)
  • [term]: any (optional) - the [term] key is the value of t and the value is the value of the data in the AppCon that is related to the Term (from data arg)
  • [action]: any (optional) - the [action] key is the value of a and the value is the value of the data in the AppCon that is related to the Action (from data arg)
  • [orient]: any (optional) - the [orient] key is the value of o and the value is the value of the data in the AppCon that is related to the Orient(ation) (from data arg)

Clearing the Component on an AppCon

Sometimes we may want to explicitly clear the Component when an Application Context is set on the TAO. The Adapter allows us to set a "Component handler" for an empty component by passing an empty or omitting the second argument to addComponentHandler:

import MyComponent from './MyComponent';

const adapter = new Adapter(TAO);
adapter
  .addComponentHandler({ t: 'Space', a: 'View', o: 'Portal' }, MyComponent)
  .addComponentHandler({ t: 'Space', a: 'List', o: 'Portal' }, null)
  .addComponentHandler({ t: 'Space', a: 'Edit', o: 'Portal' }); // equivalent to above

TAO.setCtx({ t: 'Space', a: 'View', o: 'Portal' }, Space); // <-- MyComponent set is current for Adapter - rendered in any Reactor using the Adapter

TAO.setCtx({ t: 'Space', a: 'Edit', o: 'Portal' }, Space); // <-- null set as current for Adapter - any Reactor using this Adapter will render null children

Removing a ComponentHandler

Just like you can remove a handler from the TAO, the Adapter allows you to remove a Component handler by using removeComponentHandler. Because you will generally not add a Component handler with an anonymous inline function, it's likely you will easily have a reference to the Components constructor even if that reference is imported.

import MyComponent from './MyComponent';

const adapter = new Adapter(TAO);
adapter.addComponentHandler({ t: 'Space', a: 'View', o: 'Portal' }, MyComponent);// somehwere else
adapter.removeComponentHandler({ t: 'Space', a: 'View', o: 'Portal' }, MyComponent);

Remove ALL ComponentHandler call

As a convenience, the Adapter allows you to remove all trigrams attached to a Component handler by passing an empty first argument to the removeComponentHandler method:

import MyComponent from './MyComponent';

const adapter = new Adapter(TAO);
adapter
  .addComponentHandler({ t: 'Space', a: 'View', o: 'Portal' }, MyComponent)
  .addComponentHandler({ t: 'Space', a: 'View', o: 'Admin' }, MyComponent)
  .addComponentHandler({ t: 'Space', a: 'List', o: 'Portal' }, MyComponent)
  .addComponentHandler({ t: 'Space', a: 'List', o: 'Admin' }, MyComponent);// somehwere else
adapter.removeComponentHandler({}, MyComponent); // <--- Adapter no longer has any handlers for MyComponent
// OR
adapter.removeComponentHandler(, MyComponent); // <--- Adapter no longer has any handlers for MyComponent

Special Note: as an individual Adapter is managing the handler functions added to the TAO, this way of removing handlers is scoped only to the individual Adapter.

Succinct ComponentHandlers

The Adapter provides 2 mechanisms to reduce the verbosity when working with Component handlers.

Matrixed trigrams

Because we may want to use the same Component to handle several different Application Contexts, the Adapter allows the trigram values to be Arrays to capture a matrix of trigrams to handle by our Component. When using this mechanism, the Adapter will use the ternary Cartesian Product of all possibilities and add handlers to the TAO for each.

import Form from './Form';

const adapter = new Adapter(TAO);
adapter.addComponentHandler({ term: 'Space', action: ['New', 'Edit'], orient: 'Portal' }, Form);

TAO.setCtx({ t: 'Space', a: 'New', o: 'Portal' }); // <-- shows Form with empty fields to create a new Space
TAO.setCtx({ t: 'Space', a: 'Edit', o: 'Portal' }, Space); // <-- shows Form to Edit the Space passed in 2nd arg

Default Context

Because we will usually use a single Adapter to manage a set of Components for our React App in some area of similarity, the Adapter allows setting the default context used by an Adapter to avoid repetitive setting of the same aspects when adding or removing Component handlers.

There are 2 ways to set the default context:

  • defaultCtx setter property
  • setDefaultCtx method which is fluent chainable by returning a reference to the Adapter you've seen this used in the Intro to @tao.js React examples

Additionally, the Adapter has a getter property defaultCtx which returns a copy of the value of the default context set by one of the methods above so that it cannot be modified outside of the Adapter.

When using the default context, the Adapter will perform an Object.assign on the default context and the specific trigram used in the add or remove call so that trigram aspects passed in to the specific add or remove invocation will overwrite the default when determining the trigram that is of interest. This applies to matrix trigrams as well, and the cartesian product will be determined after the merge has taken place.

We can use the defualt context to clean up examples from above:

import View from './View';
import Form from './Form';

const adapter = new Adapter(TAO);
adapter
  .setDefaultCtx({ t: 'Space', o: 'Portal' })
  .addComponentHandler({ a: 'View' }, View)
  .addComponentHandler({ a: 'List' }, null)
  .addComponentHandler({ a: ['New', 'Edit'] , Form});

TAO.setCtx({ t: 'Space', a: 'View', o: 'Portal' }, Space); // <-- View set is current for Adapter - rendered in any Reactor using the Adapter

TAO.setCtx({ t: 'Space', a: 'New', o: 'Portal' }); // <-- shows Form with empty fields to create a new Space
TAO.setCtx({ t: 'Space', a: 'Edit', o: 'Portal' }, Space); // <-- shows Form to Edit the Space passed in 2nd arg

TAO.setCtx({ t: 'Space', a: 'List', o: 'Portal' }); // <-- null set as current for Adapter - any Reactor using this Adapter will render null children

Additional props

The Adapter allows us to pass additional props that will be added to our Components when they are instantiated in the UI by the Reactor.

To do this, we pass an Object with the desired props values in the third argument to addComponentHandler:

import Form from './Form';

const adapter = new Adapter(TAO);
adapter.addComponentHandler({ t: 'Space', a: ['New', 'Edit'], o: 'Portal' }, Form, {
  successCtx: { t: 'Space', a: 'Enter', o: 'Portal' }
});

TAO.setCtx({ t: 'Space', a: 'New', o: 'Portal' }); // <-- Reactor will render Form in UI with merged tao & data props + successCtx prop set above

Addtional props specific to handler

Because we assign the additional props when adding the Component handler, the props assignment we are making are particular to when that ComponentHandler is called.

We have 3 options to make them more general and not have to repeat them:

Special Note: it should be rather easy to add a defaultProps feature to Adapter allowing this to have another option. If that seems a need, we can do it.

results matching ""

    No results matching ""