Custom behaviors
The Hyperview client supports custom behaviors to trigger your own React Native code from user interactions. Unlike custom elements, custom behaviors extend the behavior attribute syntax so that you can attach new behaviors to any existing element.
Custom behaviors have a limitation compared to first-class behaviors. The native behaviors in Hyperview are two-way: they can be triggered from the HXML, and then modify the HXML. Custom behaviors are only one-way: they can be triggered from the HXML, but the code that executes cannot modify the HXML.
Despite this limitation, custom behaviors are useful for integrations such as:
- event tracking and logging
- client-side state management libraries like Redux
- native APIs like phone calls, share sheets, message composers, etc.
Custom behaviors are registered with the Hyperview
component via the behaviors
prop.
Property | Type | Required | Description |
---|---|---|---|
behaviors | array | No | Array of objects with "action" and "callback" properties |
Each registered behavior consists of two properties:
action
: The name of the action that will trigger the custom behaviorscallback
: The function to run when the behavior triggers. The callback takes one param: the XML element that triggers the behavior.
To trigger the custom behavior, use the registered action name as the action
attribute in a <behavior>
element. For example, if you registered an action with the name foo
, you would trigger it like this:
<text>
<behavior trigger="press" action="foo" />
Test
</text>
Note that custom behaviors could be used with any trigger
attribute. You can trigger custom behavior on load, long press, etc. You can also add extra namespaced attributes to the behavior attributes. These will get passed to the callback as part of the XML element:
<doc xmlns:foo="https://instawork.com/foo">
<text>
<behavior trigger="press" action="foo" foo:param1="bar" />
Test
</text>
</doc>
Below are some examples of custom behaviors used at Instawork to build our mobile apps:
Event logging
If you have support for event tracking (via Amplitude or another service) available in your React Native code, you can specify when to log events with a custom behavior in HXML. Instawork uses Amplitude (hence the Amplitude naming conventions), but any logging system can be exposed in HXML using this technique.
To hook into Amplitude, pass a custom behavior to the Hyperview
component:
import Amplitude from 'amplitude'; // your custom tracking library
const amplitudeBehavior = {
action: 'amplitude',
callback: (element: Element) => {
const NAMESPACE_URI = 'https://instawork.com/hyperview-amplitude';
const name = element.getAttributeNS(NAMESPACE_URI, 'event');
if (name) {
const propNode = element.getAttributeNodeNS(NAMESPACE_URI, 'event-props');
const properties = propNode ? JSON.parse(propNode.value) : undefined;
Amplitude.logEvent({ name, properties });
}
},
};
function Screen({ url }) {
return (
<Hyperview
entrypointUrl={url}
fetch={fetch}
behaviors={[amplitudeBehavior]}
/>
);
}
To use this custom behavior, add a <behavior>
element with action="amplitude"
. The attributes amp:event
and amp:event-props
specify the event name and event properties to log when the behavior executes.
<view xmlns:amp="https://instawork.com/hyperview-amplitude">
<behavior trigger="load" action="amplitude" amp:event="main-screen/view" />
<text>
<behavior
trigger="press"
action="amplitude"
amp:event="main-screen/next/press"
amp:event-props="{"id":123}"
/>
Next
</text>
</view>
In this example, we log main-screen/view
to Amplitude when the view loads. When the user presses the "Next" button, we log main-screen/next/press
with the event props id = 123
.
Note that amplitude behaviors require that the HXML doc define a namespace for https://instawork.com/hyperview-amplitude
.
Phone calls
If your app needs to allow the user to make phone calls, integrate the react-native-communications
library and then add a custom behavior to trigger the system's phone caller.
import { phonecall } from 'react-native-communications';
const NAMESPACE_URI = 'https://instawork.com/hyperview-phone';
const phoneCallBehavior = {
action: 'phone',
callback: (element: Element) => {
const number = element.getAttributeNS(NAMESPACE_URI, 'number');
if (number) {
phonecall(number);
}
},
};
function Screen({ url }) {
return (
<Hyperview
entrypointUrl={url}
fetch={fetch}
behaviors={[phoneCallBehavior]}
/>
);
}
To use this custom behavior, add a <behavior>
element with action="phone"
. The attributes phone:number
specifies the number to call.
<view xmlns:phone="https://instawork.com/hyperview-phone">
<text>
<behavior trigger="press" action="phone" phone:number="5554443214" />
Call (555) 444-3214
</text>
</view>
The above example would show a UI to confirm the call to the given number.
Redux actions
If you're adding Hyperview to an existing React Native + Redux app, it can be useful to dispatch Redux actions from Hyperview screens. A custom behavior supports Redux action dispatch by adding a custom behavior to the Hyperview
component.
Configuring the client
To hook into Redux, add a custom behavior that reads the action
and extra
attributes
import { dispatch } from './redux'; // instantiated redux store for the app
import type { Element } from 'hyperview';
const NAMESPACE_URI = 'https://instawork.com/hyperview-redux';
const reduxBehavior = {
action: 'redux',
callback: (element: Element) => {
const reduxAction = element.getAttributeNS(NAMESPACE_URI, 'action');
const extraNode = element.getAttributeNodeNS(NAMESPACE_URI, 'extra');
if (reduxAction) {
const extra = extraNode ? JSON.parse(extraNode.value) : null;
dispatch({
type: reduxAction,
...extra,
});
}
},
};
function Screen({ url }) {
return (
<Hyperview entrypointUrl={url} fetch={fetch} behaviors={[reduxBehavior]} />
);
}
To trigger a Redux action from HXML, add a <behavior>
element with action="redux"
. The attributes redux:action
specifies the action type, and redux:extra
contains an encoded JSON object of action prioerties.
<view xmlns:redux="https://instawork.com/hyperview-redux">
<text>
<behavior
trigger="press"
action="phone"
redux:action="TOAST/SHOW_TOAST"
redux:extra="{"payload":{"toast":{"colorScheme":"positive","message":"Hello World!"}}}"
/>
Dispatch Redux
</text>
</view>
The above example would dispatch a Redux action with type TOAST/SHOW_TOAST
and extra properties {'payload': {'toast': {'colorScheme': 'positive', 'message': 'Hello World!'}}}
. In the Instawork app, this action would trigger a temporary toast to slide down from the top of the screen.