Original Usage with React.js
This is a description 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.
It's assumed you have read and are familiar with the Basics guide. If not, then please go back and read through that before trying to learn how to use tao.js with React.
While tao.js itself is client-agnostic, we provide packages to make it
seemlessly work with client UI libraries and frameworks.
To start we only have an implementation for React.js. Upcoming and asking for volunteers to help with packages to integrate:
- Vue.js
- Angular.js
- Ember.js
- Other UI frameworks (please help)
tao.js works seamlessly well with React given the philosophy of building
reactive applications at the heart of building applications with tao.js.
To work with React, we make sure to install the @tao.js/react package:
npm install --save @tao.js/core @tao.js/react
@tao.js/core is listed as a peerDependency for the react package so you
must install that as well or the package won't work.
There are 2 items we will import and work with to integrate tao.js within our
React UI:
- Adapter - a
classthat turns our ReactComponents into Handlers for AppCons - Reactor - a React
Componentthat uses aAdapterto react to AppCons in order to render your ReactComponents into the UI
Example
Here's a simple example of using a Adapter and Reactor to control display of Components
in the UI. We'll use the same Example Application
to illustrate integrating React here.
Example Directory Structure
Part of the Application deals with Spaces so it has the following directory:
src/
+- components/
+- Space/
- index.js
- ErrorMessage.js
- Form.js
- List.js
- View.js
- App.css
- App.js
Defining React Components
The Component definitions for ErrorMessage, Form, List and View define basic
React Components (both functional and class) and are not aware of nor dependent upon
the @tao.js/react package.
They are making use of the TAO export from @tao.js/core in
order to set the Application Context.
src/components/space/List.js
Here is an example of functional Components:
import React from 'react';
import TAO from '@tao.js/core';
const SpaceItems = ({ spaces }) =>
spaces.map(s => (
<li key={s.id}>
<button
onClick={e =>
TAO.setCtx({ t: 'Space', a: 'Enter', o: 'Portal' }, { Space: s })
}
>
{s.name}
</button>
</li>
));
const SpaceList = ({ Space }) => (
<div>
<h1>Current list of Spaces</h1>
<h3>
<button onClick={e => TAO.setCtx({ t: 'Space', a: 'New', o: 'Portal' })}>
New
</button>
</h3>
<ul>
<SpaceItems spaces={Space} />
</ul>
</div>
);
export default SpaceList;
src/components/space/Form.js
Here is an example full Component for a simple form used to Edit a Space or Create
a new Space.
import React, { Component } from 'react';
import TAO from '@tao.js/core';
class SpaceForm extends Component {
constructor(props) {
super(props);
this.state = Object.assign(
{
name: '',
description: ''
},
props.Space
);
}
handleChange = event => {
const target = event.target;
const val = target.type === 'checkbox' ? target.checked : target.value;
const name = target.name;
this.setState({
[name]: val
});
};
handleSubmit = event => {
event.preventDefault();
const { a, Space:origSpace } = this.props;
const updatedSpace = this.state;
const isNew = a === 'New';
const saveAction = isNew ? 'Add' : 'Update';
// TAO.setCtx will use positional args to set the AppCtx data
TAO.setCtx({ t: 'Space', a: saveAction, o: 'Portal' }, origSpace, updatedSpace);
};
handleCancel = event => {
event.preventDefault();
const { Space: { id } = {} } = this.props;
// if tao.a == 'New' then id == undefined
TAO.setCtx({ t: 'Space', a: 'Find', o: 'Portal' }, { Find: { id } });
};
render() {
const { a } = this.props;
const Space = this.state;
const isNew = a === 'New';
return (
<div>
<h1>
{a} Space {Space.name ? `- ${Space.name}` : ''}
</h1>
<form name={`${a}Space`} onSubmit={this.handleSubmit}>
{isNew ? null : <input type="hidden" name="id" value={Space.id} />}
<label htmlFor="name">Name:</label>
<input
type="text"
name="name"
value={Space.name}
onChange={this.handleChange}
/>
<br />
<label htmlFor="description">Description:</label>
<textarea
name="description"
value={Space.description}
onChange={this.handleChange}
/>
<br />
<input type="submit" value="Save" /> <button
onClick={this.handleCancel}
>
Cancel
</button>
</form>
</div>
);
}
}
export default SpaceForm;
Wiring up Components as Handlers
We then use a Adapter to wire up the Space Components as Handlers for AppCons
generated through the course of interacting with the application, and expose a
Reactor that will embed the Components provided by the Adapter into the React UI.
src/components/space/index.js
import React from 'react';
import TAO, { AppCtx } from '@tao.js/core';
import { Adapter, Reactor } from '@tao.js/react';
import List from './List';
import View from './View';
import Form from './Form';
import ErrorMessage from './ErrorMessage';
// chain entering a Space with showing the View
TAO.addInlineHandler(
{ t: 'Space', a: 'Enter', o: 'Portal' },
(tao, { Space }) => {
return new AppCtx('Space', 'View', 'Portal', { Space });
}
);
const spaceAdapter = new Adapter(TAO);
spaceAdapter
.setDefaultCtx({ term: 'Space', orient: 'Portal' })
.addComponentHandler({ action: 'List' }, List)
.addComponentHandler({ action: 'View' }, View)
.addComponentHandler({ action: ['New', 'Edit'] }, Form);
const messageAdapter = new Adapter(TAO);
messageAdapter.addComponentHandler({ term: 'Space', action: 'Fail' }, ErrorMessage);
const SpaceContainer = () => (
<div>
<Reactor key="spaceMessages" adapter={messageAdapter} />
<Reactor key="spaceComponents" adapter={spaceAdapter} />
</div>
);
export default SpaceContainer;
Including Space Components in the App
Our main App Component needs to then include the ability to view Spaces so
we define it like this:
src/App.js
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
// import the SpaceContainer
import SpaceContainer from './components/Space';
class App extends Component {
render() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h1 className="App-title">Welcome to React</h1>
</header>
<SpaceContainer />
</div>
);
}
}
export default App;
Above you will see that App.js imports the default export of the ./Space directory,
which is exporting a Reactor Component that it embeds in the main UI.
The Reactor's Adapter will react to AppCon changes and tell the Reactor which
Component to render or none if the AppCon doesn't call for one.
More Details
Now that we have an overall understanding of how to integrate tao.js into our React Apps, we can follow the Adapter and Reactor guides to learn more about them individually.