Links

Add custom components via CLI

Uiflow allows you to register your own custom React UI and logic components by providing you a live development server and the library publisher via a single @uiflow/cli package.

Prerequisites

  • A Uiflow account
  • @uiflow/cli package installed

Getting started

Let’s start by creating and initializing your project which will contain your custom library.
mkdir my-custom-components
cd my-custom-components
npm init -y
npm install -D typescript
npm install @uiflow/cli
# This will setup your library
npx uiflow create

Register UI component

All your custom components should be placed inside their own directory with an index entry point under the components directory in your project, eg.
<rootDir>/components/MyComponent/index.ts
This structure will also allow you to place component-related assets under their own directory.
Let’s create a new component file
mkdir -p components/MyUIComponent
cd components/MyUIComponent
touch index.tsx
and let’s register our first 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,
},
],
});
Now we can start testing our component in the Uiflow Studio by running npm start. This will open the development app with your registered components which you can drag and drop in the preview.image
By using the component option we simply pass declared props to your component, but if you need more control like for example emitting some internal state from your component, you can use the render option.
Let’s say we would like to extend our simple button and expose in the flow 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}
/>
)
}
});
Now the internal state is exposed and can be passed to other components.

Property Definition options

Name
Type
Description
Name
String
Property name
Type
call | event | boolean | number | string | object | image | video | color | boolean[] | number[] | string[] | object[]
Property type
label
String
Custom property label (if missing, the name will be used)
descriptions
String
Property description for documentation
options
Array<String>
In case the type is string, you can pass an array of options to display them as a select
arguments
Array<PropertyDefinitions>
In case the type is event, you can pass an array of other properties which will define arguments of your event (argument property cannot be of type event).
onEmit
Function
In case the type is event, you can pass a custom emit logic. Example: onEmit({ args, emit }) { emit('onClick', { prop1: args[0] }); }

Register Logic component

To define a logic component you can specify different blocks containing various logic. Here is an example of a component containing a simple sum logic.
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

Blocks are categorized by this schema
Here below an example for each of them

Action Block

{
input: {
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' });
},
},
},

Action Block with async callback

{
input: {/* same as above */}
output: {
type: PropType.Event,
name: 'onResult',
arguments: [
{
name: 'result',
type: PropType.String,
},
]
}
}

Getter Block

{
output: {
type: PropType.Number,
name: 'getMyValue',
onEmit({ props }) {
return props.myValue;
}
}
},

Transform Block

{
input: [
{
name: 'input1',
type: PropType.Number,
},
{
name: 'input2',
type: PropType.Number,
},
],
output: {
type: PropType.Number,
name: 'myResult',
onEmit({ inputs }) {
return inputs.input1 + inputs.input2;
}
}
},

Event Block

{
output: {
type: PropType.Event,
name: 'onCallback',
arguments: [
{
name: 'result',
type: PropType.Number,
},
],
}
},

Component options

Name
Type
Description
name
String
Component name
description
String | Markdown
Component description for documentation
icon
String
Component icon. You can pass SVG code as a string in order to display it in our component catalog
component
React.ComponentType
React component to render
render
Function
Custom component render (in alternative to component)
blocks
Array<BlockType>
Component blocks (to use in the Flow panel)
props
Array<PropertyType>
Component properties.
styleProps
Array<PropertyType>
Component style properties
order
Number
Order the component in the library category (the highest number is the latest)
hasPlaceholder
Function
Use this method to validate your UI components in Studio and return a string that will be shown (only in Studio) in case conditions are not met (eg. invalid props, no children).
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

Component versioning

When you update the definition of your component (eg. adding a new prop) try to always create a backward-compatible update as the previous versions of your component will keep using the previous definition.
// 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 what you can do is deprecate the old version and start a new one from scratch. In this case, the previous version of the component won’t be available in the catalog but by keeping it in your library existing components on your app will continue to function properly.
// 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>
)
}
});
// 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 library

In order to publish your library and make it available for everyone in your organization all you have to do is to run npm run publish and follow the CLI instructions.