Links

(Update WIP) Add custom components via CLI

You can use Uiflow's live development server and library publisher to register custom React UI and logic components via @uiflow/cli npm package. This enables you to add custom components to Uiflow, so you can add more nuanced functionality to your applications.

Prerequisites

You must complete the following prerequisites to add custom components to Uiflow via CLI:

Install the Uiflow CLI package

To install the @uiflow/cli package:
  1. 1.
    Open a command window.
  2. 2.
    Enter the commands below into your command line:
    mkdir my-custom-components
    cd my-custom-components
    npm init -y
    npm install -D typescript
    npm install @uiflow/cli
    # This sets up your library
    npx uiflow create
After the package is installed, follow the on-screen prompts to set up a Uiflow CLI library.

Create a directory for custom components

To stay organized, create a components directory in your project and add a sub-directory with an index entry point to store your custom components. This structure enables you to place component-related assets in their own directory.
To create your component directory:
  1. 1.
    Open a command window.
  2. 2.
    Enter the commands below into your command line:
    mkdir -p components/MyUIComponent
    cd components/MyUIComponent
    touch index.tsx
Once you complete the steps above, your new directory path should appear as:
<rootDir>/components/MyComponent/index.tsx

Register components

To make your custom component accessible in Uiflow during development, you must register it in the component code. To register your components:
  1. 1.
    Open the index.tsx file you created in the Create a directory for custom components section in a text/code editor.
  2. 2.
    Code your custom component in the index.tsx file. If you already have the code for your custom component, copy and paste it into the file.
  3. 3.
    Save your index.tsx file.
You can register two types of components via CLI:

Register a UI component

This example shows how to register a custom Simple Button UI component:
import * as React from 'react';
import { PropType, registerComponent } from '@uiflow/cli';
export type Props = React.HTMLProps<HTMLButtonElement> & {
label: string;
};
const SimpleButton: React.FC<Props> = ({ className, onClick, label }) => {
return (
<button className={className} onClick={onClick}>
{label}
</button>
);
}
export default registerComponent('my-ui-component', {
component: SimpleButton,
name: 'Simple Button',
props: [
{
name: 'label',
type: PropType.String,
value: 'My Label',
},
{
name: 'onClick',
type: PropType.Event,
},
],
});

Properties

The following table describes a few major properties of custom UI components:
Name
Type
Description
name
String
Name of the property.
type
call | event | boolean | number | string | object | image | video | color | boolean[] | number[] | string[] | object[]
Type of property.
label
String
Custom property label. If missing, the label appears as name.
descriptions
String
Description of the property's purpose and use.
options
Array<type>
When the type is string, you can pass an array of options to display them as a select.
arguments
Array<type>
When the type is event, you can pass an array of other properties to define the arguments in your event.
Note: arguments cannot be a type of event.
onEmit
Function
When the type is event, you can pass a custom emit logic. For example:
onEmit({ args, emit }) { emit('onClick', { prop1: args[0] }); }

Register a logic component

Custom logic components are comprised of different blocks containing various logic. This example shows how to register a custom sum logic component, which you can use to add two numbers together:
import { PropType, registerComponent } from '@uiflow/cli';
export default registerComponent('my-logic-component', {
name: 'Sum',
blocks: [
{
input: {
type: PropType.Call,
name: 'sum',
arguments: [
{
name: 'value1',
type: PropType.Number,
value: 0,
},
{
name: 'value2',
type: PropType.Number,
value: 0,
},
],
onEmit({ inputs, emit }) {
const result = inputs.value1 + inputs.value2;
emit('onResult', { result });
},
},
output: {
type: PropType.Event,
name: 'onResult',
arguments: [
{
name: 'result',
type: PropType.Number,
},
]
}
},
]
});

Block types

There are five block types you can add to logic components:
  • Action block
  • Action block with async callback
  • Getter block
  • Transform block
  • Event block
For samples of each block type, see the sections below.
Blocks included in your logic component appear on your Uiflow component according to this schema:
Sample action block
Action blocks enable your component to update properties and emit events.
<strong> input: {
</strong> type: PropType.Call,
name: "callName",
arguments: [
{
name: 'input1',
type: PropType.Number,
value: 123,
},
{
name: 'input2',
type: PropType.Number,
},
],
// Here you can update your props and emit events
onEmit({ setProps, inputs, emit }) {
setProps({ prop1: inputs.input1, prop2: inputs.input2 });
emit('onResult', { result: 'my result' });
},
},
}
Sample action block with async callback
Action blocks with async callbacks enable your component to update properties and emit events.
{
input: {/* same as above */}
output: {
type: PropType.Event,
name: 'onResult',
arguments: [
{
name: 'result',
type: PropType.String,
},
]
}
}
Sample getter block
Getter blocks enable your component to emit property values.
{
output: {
type: PropType.Number,
name: 'getMyValue',
onEmit({ props }) {
return props.myValue;
}
}
}
Sample transform block
Transform blocks enable your component to emit the combined value of multiple inputs.
{
input: [
{
name: 'input1',
type: PropType.Number,
},
{
name: 'input2',
type: PropType.Number,
},
],
output: {
type: PropType.Number,
name: 'myResult',
onEmit({ inputs }) {
return inputs.input1 + inputs.input2;
}
}
}
Sample event block
Event blocks enable your component to emit events.
{
output: {
type: PropType.Event,
name: 'onCallback',
arguments: [
{
name: 'result',
type: PropType.Number,
},
],
}
}

Component configuration options

The following table outlines a few major component configuration options you can code into your custom components:
Name
Type
Description
name
String
Name of the component.
description
String | Markdown
Description of the property's purpose and use.
icon
String
Icon of the component. You can pass SVG code as a string to display the icon in the Uiflow component catalog.
component
React.ComponentType
React component you want to render.
render
Function
Custom component you want render. This option is an alternative to component.
blocks
Array<BlockType>
Component blocks you can use in the Logic view.
props
Array<PropertyType>
Properties of the component.
styleProps
Array<PropertyType>
Style properties of the component.
order
Number
Where the component appears in the library category. The highest number is the latest component.
hasPlaceholder
Function
This method validates your UI components in Uiflow and returns a string (only in Uiflow) when the case conditions are not met (e.g., invalid props, no children). For example:
hasPlaceholder({ props }) { if (!props.label) { return “Label can’t be empty”; } }
deprecated
Boolean
Declare the component as deprecated and hide it from the component catalog.

Test your component

Now that you have registered and configured your component, you can test it in Uiflow Studio by running npm start in your command line. This opens a Uiflow development app in your web browser where you can drag-and-drop your registered components into the app via the Design view or Logic view.
The component option enables you to pass declared props to your component. If you need more control (e.g. you need to emit an internal state from your component), you can use the render option.
This example shows how to extend the Simple Button component to display a counter node in Uiflow that tracks how many times the button was clicked:
import * as React from 'react';
import { PropType, registerComponent } from '@uiflow/cli';
// ...
export default registerComponent('my-ui-component', {
name: 'Simple Button',
props: [
// ...
{
name: 'onClick',
type: PropType.Event,
arguments: [
{
name: 'counter',
type: PropType.Number,
}
],
onEmit({ args, emit }) {
// The `newCounter` is passed as the first argument of `props.onClick`
const newCounter = args[0];
// Specify the event name and arguments to emit
emit('onClick', { counter: newCounter });
}
},
],
render({ props, className }) {
const [counter, setCounter] = React.useState(0);
const onClick = () => {
const newCounter = counter + 1;
setCounter(newCounter);
props.onClick(newCounter);
}
return (
<SimpleButton
className={className}
label={props.label}
onClick={onClick}
/>
)
}
});

Component versioning

An application that uses your component is inherently dependent on that component’s definition (e.g. the component’s properties). When you create a new version of your component, you may cause compatibility issues if you change parts of the definition on which the application relies (e.g., you rename or delete a property, change its type, etc.). To avoid compatibility issues with your new component version, try to create a backward-compatible update.
The following example shows a successful backward-compatible update for a custom component:
// Initial component
export default registerComponent('my-component', {
name: 'My Component',
props: [
{
name: 'label',
type: PropType.String,
}
],
render({ props }) {
return (
<div>{props.label}</div>
)
}
});
// Updated component
export default registerComponent('my-component', {
name: 'My Component',
props: [
{
name: 'label',
type: PropType.String,
},
// Added new property
{
name: 'content',
type: PropType.String,
},
],
render({ props }) {
// In previous versions, the `props.content` will be undefined
return (
<div>{props.label}</div>
<div>{props.content || 'No content'}</div>
)
}
});
When backward-compatibility is not possible, deprecate the old version and start a new one from scratch. After you deprecate the old version, it is no longer available in the catalog. However, keeping it in your library enables existing components in your app to continue functioning properly.
The example below demonstrates how to deprecate your old component and build a new one from scratch:
// Deprecate the old component
// components/MyComponent/index.ts
export default registerComponent('my-component', {
// Deprecate the component
deprecated: true,
name: 'My Component',
props: [
{
name: 'label',
type: PropType.String,
}
],
render({ props }) {
return (
<div>{props.label}</div>
)
}
});
// Create new component version from scratch
// components/MyComponentV2/index.ts
export default registerComponent('my-component-v2', {
// You can keep the same name or change it in something like "My Component V2"
name: 'My Component',
props: [
{
name: 'content',
type: PropType.String,
}
],
render({ props }) {
return (
<div>{props.content}</div>
)
}
});

Publish the component

Once you feel satisfied with your component, you can publish it to your Uiflow library. This makes the component available in the Libraries panel for everyone in your organization.
To publish your component:
  1. 1.
    Open a command window.
  2. 2.
    Enter npm run publish into the command line.
  3. 3.
    Follow the on-page instructions.