Skip to main content

Define Component

This page contains a detailed API reference for the defineComponent function.

Import​

import { defineComponent } from 'froyojs';

Reference​

defineComponent​

Define a new Froyo component.

Type​

function defineComponent<
TThis extends ComponentThis,
TOptions extends ComponentOptions<TThis>
>(options: TOptions): ComponentConstructor;

Details​

This function requires a single argument which is an object to configure the component. It returns a component constructor which can be used to create component instances.

If you are using TypeScript, both generic arguments are optional. Providing the first is highly recommended; it defines the types for the this keyword within options. When provided, this argument will add granular control, enhanced type safety, and rich autocomplete to the component options. It will also add more specific types for consumers interacting with the component constructor and instance. The second argument defines the type for the options themselves and is often unnecessary to be provided explicitly.

Example​

const FrozenYogurt = defineComponent({
name: 'FrozenYogurt',
state: {
flavor: {
type: String,
default: 'Vanilla',
},
},
nodes: {
button: {
type: 'query',
selector: 'button.toggle',
};
message: {
type: 'query',
selector: '.message',
},
},
events: {
button() {
return {
click: () => {
this.$state.flavor = this.$state.flavor === 'Vanilla' ? 'Chocolate' : 'Vanilla';
};
};
},
},
render: {
message() {
return `My favorite flavor is: ${this.$state.flavor}`;
},
},
});

constructor​

The constructor for a component instance.

Type​

interface ComponentConstructor {
new (
root: string | Element,
state?: Record<string, any>
): ComponentInstance;
}

Details​

A component constructor is generated by the defineComponent function. It is used to generate new instances for that component definition.

The first argument of the constructor is required. It must be an element or a query selector for a valid element within in the DOM. The second argument is optional and is responsible for setting the initial state of the component.

Example​

const instance = new FrozenYogurt(document.querySelector('#root'), {
flavor: 'Chocolate',
});

displayName​

The human-friendly name of the component.

Type​

interface ComponentConstructor {
get displayName(): string;
}

Details​

This readonly property is the name of the component defined by the name option.

Example​

console.log(FrozenYogurt); // "FrozenYogurt"

this​

The internal component instance.

Type​

type ComponentThis<T> = {
$root: Element;
$state: Record<string, any>;
} & Record<string, any>;

Details​

this is an object that is bound to the component options and contains the following properties:

  • $root: the root element (defined by the constructor)
  • $state: the reactive state (defined by the state option)
  • ...: properties defined in nodes, components, methods.

All functional component options (e.g. methods, components, events, render, hooks) are automatically bound to this object. This means that referencing the this keyword from within them will give you access to the data above.

Since the properties from the nodes, components and methods options are spread on this object, their keys must be unique or they will be overridden by one another.

State properties are reactive, updating the value of a state property assigned to this.$state will trigger a component update.

Non-state properties can also be added or updated directly, however doing so will not trigger a component update.

Example​

defineComponent({
hooks: {
$setup() {
console.log(this); // { $root: Element, ... }
},
},
});

Configuration Options​

name​

Define name of the component.

Type​

type ComponentOptions = {
name?: string;
};

Details​

This optional property defines a human-friendly name for the component and is used primarily for error messaging.

If a name is not provided the component will be labelled: "Unnamed component".

Example​

const FrozenYogurt = defineComponent({ name: 'FrozenYogurt' });

console.log(FrozenYogurt.displayName); // "FrozenYogurt"

state​

Declare and configure the reactive state of the component.

Type​

type ComponentOptions = {
state?: { [key: string]: StateOption };
};

type StateOption<T = any> = {
type?: { new (): T };
required?: true;
default?: T;
readonly?: true;
};

Details​

The state option takes an object with each key representing the name of the property.

The value of each property must be an object with the following options:

  • type (optional): Specifies the expected type. Can be one of the following native constructors: String, Number, Boolean, Array, Object, Date, Function, or any custom constructor function. The value of each state is compared to this type and a warning will be thrown in non-production environments if it does not match.
  • default (optional): Specifies a default value. The property will be set to this value automatically when the value is undefined.
  • required (optional): Defines if the state property is required. In non-production environments, a warning will be thrown if the property value is undefined.
  • readonly (optional): Defines if the state property is readonly. If enabled, consumers will be able to read the value of the property, but will not be able to update the value of the property externally.

Example​

const FrozenYogurt = defineComponent({
state: {
price: {
type: Number,
default: 1.99,
},
flavor: {
type: String,
required: true,
},
},
});

See also:​


nodes​

Declare and configure the DOM nodes for the component.

Type​

interface ComponentOptions {
nodes?: { [key: string]: NodeOption };
}

type NodeOption =
| {
type: 'text';
value?: string;
}
| {
type: 'element';
tagName: string;
className?: string;
attributes?: Record<string, string>;
content?: string;
}
| {
type: 'svg';
tagName: string;
className?: string;
attributes?: Record<string, string>;
content?: string;
}
| {
type: 'query';
selector: string;
optional?: true;
scope?: ($root: Element) => Element | Document;
}
| {
type: 'query-all';
selector: string;
optional?: true;
scope?: ($root: Element) => Element | Document;
}
| {
type: 'custom';
node: (
$root: Element
) =>
| Text
| Element
| Element[]
| NodeListOf<Element>
| HTMLCollectionOf<Element>
| null;
};

Details​

The nodes option takes an object with each key representing the name of the property.

The value of each property must be an object with a type property matching one of the following types:

  • text: Creates a new text node with the following options:
    • value (optional): Specifies the initial text value of the node.
  • element: Creates a new HTML element with the following options:
    • tagName: Specifies the type of element to be created.
    • className: Specifies a CSS class name to add to the element.
    • content: Specifies a string to render within the element.
    • attributes: Specifies an object of attributes to add to the element.
  • svg: Creates a new SVG element with the following options:
    • tagName: Specifies the type of element to be created.
    • className: Specifies a CSS class name to add to the element.
    • content: Specifies a string to render within the element.
    • attributes: Specifies an object of attributes to add to the element.
  • query: Retrieves an element from the DOM with the following options:
    • selector: Specifies the CSS selector used to find the element.
    • optional (optional): Defines if the element is optional. If enabled, a warning will not be thrown if no element is found.
    • scope (optional): Declares the target of the query. By default, the root element is used. Must return an element or document.
  • query-all: Retrieves an array of elements from the DOM with the following options:
    • selector: Specifies the CSS selector used to find the elements.
    • optional (optional): Defines if the element is optional. If enabled, a warning will not be thrown if no elements are found.
    • scope (optional): Declares the target of the query. By default, the root element is used. Must return an element or document.
  • custom:
    • node: Declares custom node(s). The root element is passed as the first and only argument. When a NodeList or HTMLCollection is returned, the value is automatically converted into an array.

Example​

const FrozenYogurt = defineComponent({
nodes: {
label: {
type: 'text',
value: '$1.99',
},
cone: {
type: 'element',
tagName: 'div',
className: 'cone',
},
logo: {
type: 'svg',
tagName: 'svg',
content: '<path />',
attributes: {
width: '25px',
height: '25px',
},
},
topping: {
type: 'query',
selector: '.topping',
optional: true,
},
napkins: {
type: 'query-all',
selector: '.napkin',
scope: () => document,
},
spoon: {
type: 'custom',
node: ($root) => $root.querySelector('.spoon'),
},
},
});

See also:​


methods​

Declare reusable functions on the component.

Type​

interface ComponentOptions {
methods?: { [key: string]: (...args: any) => any };
}

Details​

The methods option takes an object with each each key representing the name of the property and each value being a function.

Each function declared on this property is added to this and is automatically bound to the internal component instance.

Example​

const FrozenYogurt = defineComponent({
state: {
lickCount: {
default: 0,
},
},
methods: {
lickIceCream() {
this.$state.lickCount++;
},
},
hooks: {
$setup() {
this.lickIceCream();
},
},
});

components​

Declare and configure other components that are used by the component.

Type​

interface ComponentOptions {
components?: {
[key: string]: (this: ComponentThis) => ComponentOption;
};
}

type ComponentOption = {
constructor: ComponentConstructor;
root: Element | string;
state?: Record<string, any>;
subscribe?: Record<string, (value: any, previousValue: any) => void>;
};

Details​

The components options takes an object with each key representing the name of the property.

The value of each property must be function that returns an object with the following options:

  • constructor: Declares the component constructor to be used.
  • root: Declares the root for the component. Must be a valid element or CSS query selector.
  • state (optional): Defines the state of the component.
  • subscribe (optional): Subscribes observers to specified state properties.

Example​

const Cone = defineComponent({
state: {
type: {
type: String,
default: 'sugar',
},
},
});

const FrozenYogurt = defineComponent({
state: {
coneType: {
type: String,
default: 'waffle',
},
},
components: {
topping: {
constructor: Cone,
root: document.querySelector('.cone'),
state: {
cone: this.$state.coneType,
},
subscribe: {
cone: (value) => {
this.$state.coneType = value;
},
},
},
},
});

See also:​


events​

Declare and attach events to DOM elements related to the component.

Type​

interface ComponentOptions<T extends ComponentNodes> {
events?: {
$window: (this: ComponentThis) => EventOption;
$document: (this: ComponentThis) => EventOption;
$root: (this: ComponentThis) => EventOption;
} & {
[key: string]: (this: ComponentThis, index?: number) => EventOption;
};
}

type EventOption = {
[key: string]: (event: Event) => void;
};

Details​

The events option takes an object with each key representing the name of the event target.

With the exception of $window, $document, and $root, each key must correspond to properties defined in the nodes option.

The value of each property must be a function that returns an object of event types and handlers which are assigned directly to the respective target(s).

For properties with an array of elements, the array index is passed as the first argument of the function.

Example​

const FrozenYogurt = defineComponent({
nodes: {
element: {
type: 'element',
tagName: 'button',
},
},
events: {
$document() {
return {
click: (event) => {
// ...
},
};
},
button() {
return {
focus: (event) => {
// ...
},
};
},
},
});

See also:​


render​

Type​

interface ComponentOptions {
render?: {
$root: (this: ComponentThis) => RenderOptionElement;
} & {
[key: string]: (
this: ComponentThis,
index?: number
) => string | RenderOptionElement;
};
}

type RenderOptionElement = {
attributes?: Record<string, string | boolean | null | undefined>;
classes?: Record<string, boolean>;
content?: string;
style?: Record<string, string | null | undefined>;
};

Details​

The render option takes an object with each key representing the name of the property. The value must be a function that returns value relative to the node type.

For Text nodes, the function must return a string. This string is applied directly to the node's value.

For Element nodes, the function can return a string or an object. If a string is returned, it will be applied directly to the element's innerHTML. If an object is returned it can have any of the following options:

  • attributes: Defines an object with each key representing the name of the HTML attribute and its value matches one of the following types:
    • string: Applies the value directly to the attribute
    • true: Adds the attribute to the element
    • false, null: Removes the attribute from the element
    • undefined: Ignores the entry and nothing changes
  • classes: Defines an object with each key representing the name of a CSS class name and its value matches one of the following types:
    • true: Adds the class name
    • false: Removes the class name
  • style (HTML elements only): Defines an object of inline styles to apply to the element. Each key represents the class name to be applied, while the value must be must be one of the following:
    • string: Applies the value directly to the property
    • null: Removes the property from the element
    • undefined: Ignores the entry and nothing changes
  • content: Defines the content of the element. The value must be a string that is used to set the inner HTML of the element.

The $root property is reserved and should be used to render the root element. All other keys must correspond to properties defined in nodes.

For properties which are an array of elements, the data will be applied directly to every element. Additionally, the index of the element is passed as the first argument of the function.

Example​

defineComponent({
nodes: {
contentOnly: {
type: 'element',
tagName: 'span',
},
optionsObject: {
type: 'element',
tagName: 'svg',
},
},
render: {
contentOnly() {
return '...'; // applies to innerHTML
},
optionsObject() {
return {
attributes: {
'<attribute-name>': '...', // sets the value
'<attribute-name>': true, // adds boolean attribute
'<attribute-name>': false, // removes boolean attribute
'<attribute-name>': null, // removes the attribute
'<attribute-name>': undefined, // ignores the update
},
classes: {
'<class-name>': true, // adds class
'<class-name>': false, // removes class
},
style: {
'<property-name>': '...', // sets the value
'<property-name>': null, // removes the property
'<property-name>': undefined, // ignores the update
},
content: '...', // applies to innerHTML
};
},
},
});

See also:​


hooks​

Define functions that hook into component lifecycle events and react to state changes.

Type​

interface ComponentOptions {
hooks?: {
$setup: (this: ComponentThis) => void;
$teardown: (this: ComponentThis) => void;
} & {
[key: string]: (
this: ComponentThis,
value: any,
previousValue: any
) => void;
};
}

type ComponentThis = ComponentState & ComponentNodes & ComponentData;

Details​

The hooks option takes an object with each key representing the name of the hook and each value is a function.

A handful of properties have reserved names and are called at specific times during the component lifecycle;

  • $setup: called once when the component initializes
  • $teardown: called once when the component is destroyed

All other keys must correspond to properties defined in state. These functions are called when the value of that state has changed. The current and previous value are passed to the function as the first and second argument respectively.

Example​

const FrozenYogurt = defineComponent({
state: {
flavor: {
type: String,
default: 'vanilla',
},
},
hooks: {
$setup() {
// Perform setup tasks ...
},
$teardown() {
// Perform cleanup tasks ...
},
flavor(value, previousValue) {
// The value of "flavor" changed, do something ...
},
},
});

See also:​


Component Instance​

state​

Retrieve the current state of the component.

Type​

export interface ComponentInstance {
get state(): Record<string, any>;
}

Details​

This property returns a readonly copy of the current state of the component instance.

Example​

const instance = new FrozenYogurt('#root');

console.log(instance.state); // { flavor: 'vanilla', ... }

root​

Retrieve the root element.

Type​

export interface ComponentInstance {
get root(): Element;
}

Details​

This property returns a reference to the root element.

Example​

const instance = new FrozenYogurt('#root');

console.log(instance.root); // Element

setState​

Imperatively set the internal state of the component instance.

Type​

interface ComponentInstance {
setState(stateChanges: Record<string, any>): void;
}

Details​

This method updates the state of the component instance. The one required argument must be an object that represents the state properties to be set. Each key must match a property name from the component's state definition.

If a particular state property is configured to be "readonly", any attempt to update it via this method will be ignored and a warning will be thrown.

Example​

const instance = new FrozenYogurt('#root');

instance.setState({ flavor: 'chocolate' });

See also:​


subscribe​

Bind a callback function to the changes of a specified state property.

Type​

interface ComponentInstance {
subscribe(
property: string,
observer: (value: any, previousValue: any) => void
): void;
}

See also:​

Details​

This method will bind the given observer function to a given state property. When the value of that property is changed, this function will be called with the current and previous value as the first and second argument respectively.

Example​

const instance = new FrozenYogurt('#root');

const observeFlavorChange = (value: string) => {
// "flavor" changed, do something ...
};

instance.subscribe('flavor', observeFlavorChange);

unsubscribe​

Unbind a callback function from a specified state property.

Type​

interface ComponentInstance {
unsubscribe(
property: string,
observer: (value: any, previousValue: any) => void
): void;
}

Details​

This method will unbind an observer function that was previously bound to a particular state property.

Example​

const instance = new FrozenYogurt('#root');

const observeFlavorChange = (value: string, previousValue: string) => {
// "flavor" changed, do something ...
};

instance.unsubscribe('flavor', observeFlavorChange);

destroy​

Imperatively destroy the component instance.

Type​

interface ComponentInstance {
destroy(): void;
}

Details​

When called, this method will immediately "destroy" the component instance by removing all event listeners, clearing all observers, and firing all end-of-lifecycle hooks.

Example​

const instance = new FrozenYogurt('#root');

instance.destroy();