Skip to content

Components

The Component is the fundemental building block of Noctes.jsx, at its core a Component is a Object with a render() method, optional lifecycle hook methods and optional "methods" object with user-defined methods.

ts
interface Component {
  render: RenderFunction;

  /**
   * Lifecycle hook that is called when an error occured during rendering.
   */
  onError?: (
    this: ComponentContext,
    ctx: ComponentContext,
    err: Error
  ) => void;

  /**
   * Lifecycle hook that is called during the creation of
     ComponentContext, and is the first ever hook to be called,
     even before first render.
   * This is where you should setup data in ctx or "this"
   */
  onCreated?: (
    this: ComponentContext,
    ctx: ComponentContext,
    props: Readonly<Object>
  ) => void;

  /**
   * Lifecycle hook that is called when the Component has first been rendered.
   */
  onMounted?: (
    this: ComponentContext,
    ctx: ComponentContext,
    props: Readonly<Object>
  ) => void;

  /**
   * Lifecycle hook that is called before Component re-renders.
   */
  beforeUpdate?: (
    this: ComponentContext,
    ctx: ComponentContext,
    props: Readonly<Object>
  ) => void;

  /**
   * Lifecycle hook that is called when the Component has been re-rendered.
   */
  onUpdated?: (
    this: ComponentContext,
    ctx: ComponentContext,
    props: Readonly<Object>
  ) => void;

  /**
   * Lifecycle hook that is called after Component has been unmounted,
     and before Component Instance is destroyed.
   */
  onDestroy?: (this: ComponentContext, ctx: ComponentContext) => void;

  methods?: {
    [key: string]: (this: ComponentContext, ...args: any[]) => any
  }
}

Shared Properties

Shared Properties are properties that are shared from ancestor component to descenadant component. This prevents the need of "Prop Drilling" (passing props down from Child to Child to get to its destination).

A component may only set Shared Properties of its own provides, and may only get the provides of it's ancestors. There are also Global Shared Properties (which are Shared Properties accesible at the Root Level).

If there are duplicate keys of the same on multiple levels, the nearest ancestor will take priority (or Global Properties in case that no ancestor has said key). And in the get shared property function you may define a fallback, where it would have following priority:

  1. Nearest Ancestor's Provideds
  2. Global Provides
  3. Fallback Value

The following functions may be used to Get and Set Shared Properties.

WARNING

Only use these functions in synchronous Plugin Installer, or Component Methods, Lifecycle Hook or Render Function, and shall not be used outside the scope of said functions for example not in setTimeout, setInterval, queueMicrotask or any other function that is not ran immediately.

In order to ensure that no problems arise, it is reccomended you use Component Context methods.

Refer to Component Context methods for async.

Technical Details: These functions access a variable ("currentInstance"), which is never left with a non-null variable when Call Stack is emptied, async runs in Microtasks which is queue by the Event Loop to be run when the Call Stack is emptied, therefore you'd be setting Global Shared Properties.

ts
type Key = string | symbol | number;

function setSharedProp<T>(key: Key, value: T): void

function unsetSharedProp(key: Key): void

function getSharedProp<T>(key: Key, fallback?: T): T | undefined

function listSharedProps(): Map<Key, any>

Render Slots

A Render Slot is a View that is generated and provided by the Parent Component and used by the Child Component at a location designated by the Child Component.

Example

jsx
import ChildComponent from './child.jsx'

export default {
  render(ctx, props, slots) {
    return (
    <>
    <ChildComponent>
      <slot>
        <div>Hello World</div>
      </slot>
      <slot:footer>
        This is footer text.
      </slot:footer>
    </ChildComponent>
    </>
    )
  }
}
jsx
export default {
  render(ctx, props, slots) {
    return (
    <>
    <div>Default Slot: {slots.default}</div>
    <div>Footer Slot: {slots.footer}</div>
    </>
    )
  }
}

Resulting HTML

html
<div>
  Default Slot: <div>Hello World</div>
</div>
<div>
  Footer Slot: This is footer text.
</div>

Component Context

Component Context is a proxied Component Instance, that is provided to Lifecycle Hooks, Methods and Render Function.

Component Instance are created when the first VNode for a specific component at same position appears, and is destroyed when the VNode of same component and same position is no longer found.

Component Instance carries information like Effects (reactive subscriptions), Component Data (data used by Component) and properties.

ts
type AccessKey = string | symbol | number;

interface ComponentContext { 
  /**
   * Component Methods defined in Component Interface
   */
  methods: {
    [key: string]: (this: ComponentContext, ...args: any[]) => any
  },

  /**
   * Instance Data:
   * An object used to store Instance-specific data.
     This is only utilized by the Component itself.
   */
  data: Object,

  /**
   * Shallow Readonly object of Component Properties.
   * 
   * Component Properties are passed as a Shallow Readonly object in order to
     prevent any accidental overrides (which can cause internal problems).
   */
  props: Readonly<Object>,

  /**
   * Global Properties is a regular Object, same as import { globalProperties }.
   * 
   * Global Properties are typically declared by Plugins,
     but components can declare Global Properties too.
   */
  global: {
    [key: AccessKey]: any
  },

  /**
   * Sets a Shared Property.
   * 
   * Shared Properties may only be accesed by descandent components.
   */
  $set<T>(key: AccessKey, value: T): void,

  /**
   * Deletes / unsets a Shared Property.
   * 
   * Only affects $get and $list of descandent components like $set.
   */
  $unset(key: AccessKey): void,

  /**
   * Gets a Shared Property.
   * 
   * Returns the Nearest Ancestor's value of the key,
     if none of the Ancestors have the key,
     it will return from Global Shared Properties.
   * If no value can be found in either any Ancestor or Global Shared Properties,
     it will return the Fallback Value.
   */
  $get<T>(key: AccessKey, fallback?: T): T | undefined,

  /**
   * Returns a Map of all the Shared Properties
     that this component can access (incuding values).
   */
  $list(): Map<AccessKey, any>,

  /**
   * Forces a re-render of the Component in next Animation Frame
     (wether or not properties changed or reactive dependency has been changed).
   */
  $forceUpdate(): void,

  /**
   * Gets the underlying Component Instance.
   * IMPORTANT! Use with caution.
   */
  $raw: ComponentInstance,

  /**
   * This is the default case, which has two ways of being handled:
   * Getter: If first letter is $ it indicates a global / reserved property,
     it will search in global. If not it will look in Component Methods,
     Instance Data or Component Properties (in that order).
   * 
   * Setter: If first letter is $ it indicates a global / reserved property,
     throw error. If not it will set the key provided in Instance Data
     to the value provided.
   */
  [key: AccessKey]: any
}

Render Function

This is a explanation / template of "Render Functions".

Props must not be changed, and must be immutable.

Render Functions must always return an array, wether empty or not. The array can contain VNode and null.

ts
type RenderFunction = (
  this: ComponentContext,
  ctx: ComponentContext,
  props: Readonly<Object>,
  slots: ComponentSlots
) => Fragment

Example

js
import { v } from 'noctes.jsx'

export default {
  render(ctx, props) { 
    return [
      v("div", `props.a is ${props.a}`)
    ]
  }
}

Empty Slots

Empty slots are null values inside of Fragments, used to tell Noctes.jsx that there is a VNode reserved to be in that position, that way when that VNode is mounted (for example a condition is furfilled), Noctes.jsx doesn't have to move and unmount alot of things unnecessarily.

ts
/**
 * Empty Slots are just the value ("null").
 */
type EmptySlot = null;

Fragment

A fragment is an array of VNode and Empty Slots (null) used in VNode children or returned by render functions, fragments can also be inside of other fragments.

When rendering lists alongside other elements in one Container Element, you should wrap the list in a Fragment.

ts
/**
 * Fragments are an array of either VNode, other Fragments or Empty Slots.
 * 
 * Used by Render Function, VNode Children and nested fragments
   (technically speaking, all Fragments except Root Component
   are nested fragments).
 */
type Fragment = (VNode | Fragment | EmptySlot)[];

VNode

VNodes in render functions are either of type Element Node, Component Node, string (converted to Text Node by Noctes.jsx) or array (converted to Fragment Node by Noctes.jsx).

Properties must be immutable, new properties shall be passed with a new object.

You can make Element or Component VNodes with the following functions:

ts
/* Alias: e() */
function createElement(
  tag: string,
  properties: Readonly<Object> | null,
  ...children: Fragment
): ElementNode

/* Alias: c() */
/**
 * component would only ever be string if the first character is uppercase, and is one of the Standard Components such as ("Lazy").
 */
function createComponent(
  component: Component | string,
  properties: Readonly<Object> | null,
  slots?: ComponentSlots
): ComponentNode

/* Alias: v() */
/**
 * component can only be passed as string if it's part of Standard Components such as ("Lazy"), if it's not it will be interpeted as a tag for Element Node.
 */
function createVNode(
  component: Component | string,
  properties: Readonly<Object> | null,
  slots?: ComponentSlots
): ComponentNode

/**
 * If the 2nd argument is not a direct Object (constructor is Object) it will interpit it as no properties and set children as rest of arguments.
 */
function createVNode(
  tag: string,
  ...children: Fragment
): ElementNode

function createVNode(
  tag: string,
  properties: Readonly<Object>,
  ...children: Fragment
): ElementNode