CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-riot

Simple and elegant component-based UI library

Pending
Overview
Eval results
Files

pure-components.mddocs/

Pure Components

Lightweight component pattern for simple, stateless components without full lifecycle management. Pure components are optimized for performance and simplicity, ideal for presentational components or when you need fine-grained control over rendering.

Capabilities

Pure Component Factory

Creates a pure component factory function that produces lightweight component instances.

/**
 * Lift a riot component interface into a pure riot object
 * @param func - RiotPureComponent factory function
 * @returns The original function marked as pure for internal handling
 */
function pure<
  InitialProps extends DefaultProps = DefaultProps,
  Context = any,
  FactoryFunction = PureComponentFactoryFunction<InitialProps, Context>
>(func: FactoryFunction): FactoryFunction;

Usage Example:

import { pure } from "riot";

// Create a pure component factory
const createSimpleCard = pure(({ slots, attributes, props }) => {
  return {
    mount(element, context) {
      element.innerHTML = `
        <div class="card">
          <h3>${props.title}</h3>
          <p>${props.content}</p>
        </div>
      `;
    },
    
    update(context) {
      // Update logic if needed
      if (context && context.newContent) {
        const p = element.querySelector('p');
        p.textContent = context.newContent;
      }
    },
    
    unmount(keepRootElement) {
      if (!keepRootElement) {
        element.innerHTML = '';
      }
    }
  };
});

// Use the pure component
const element = document.getElementById("card-container");
const cardComponent = createSimpleCard({
  props: { title: "Pure Card", content: "This is a pure component" }
});

cardComponent.mount(element);

Pure Component Interface

Pure components implement a simplified interface without the full RiotComponent lifecycle:

interface RiotPureComponent<Context = object> {
  /** Mount component to DOM element */
  mount(element: HTMLElement, context?: Context): void;
  
  /** Update component with new context */
  update(context?: Context): void;
  
  /** Unmount component from DOM */
  unmount(keepRootElement: boolean): void;
}

Pure Component Factory Function

The factory function receives component metadata and returns a pure component instance:

interface PureComponentFactoryFunction<
  InitialProps extends DefaultProps = DefaultProps,
  Context = any
> {
  ({
    slots,
    attributes,
    props,
  }: {
    slots?: TagSlotData<Context>[];
    attributes?: AttributeExpressionData<Context>[];
    props?: InitialProps;
  }): RiotPureComponent<Context>;
}

Pure Component Patterns

Simple Presentational Component

import { pure } from "riot";

const createButton = pure(({ props, attributes }) => {
  let element;
  
  return {
    mount(el) {
      element = el;
      element.innerHTML = `
        <button class="btn ${props.variant || 'primary'}">
          ${props.label || 'Click me'}
        </button>
      `;
      
      // Add event listeners
      const button = element.querySelector('button');
      button.addEventListener('click', props.onClick || (() => {}));
    },
    
    update(context) {
      if (context && context.label) {
        const button = element.querySelector('button');
        button.textContent = context.label;
      }
    },
    
    unmount(keepRootElement) {
      if (!keepRootElement && element) {
        element.innerHTML = '';
      }
    }
  };
});

// Usage
const buttonComponent = createButton({
  props: {
    label: "Save",
    variant: "success",
    onClick: () => console.log("Saved!")
  }
});

Data Display Component

const createDataTable = pure(({ props }) => {
  let element;
  
  return {
    mount(el, context) {
      element = el;
      this.render(props.data || []);
    },
    
    update(context) {
      if (context && context.data) {
        this.render(context.data);
      }
    },
    
    render(data) {
      const tableHTML = `
        <table class="data-table">
          <thead>
            <tr>${props.columns.map(col => `<th>${col.title}</th>`).join('')}</tr>
          </thead>
          <tbody>
            ${data.map(row => `
              <tr>${props.columns.map(col => `<td>${row[col.key]}</td>`).join('')}</tr>
            `).join('')}
          </tbody>
        </table>
      `;
      element.innerHTML = tableHTML;
    },
    
    unmount(keepRootElement) {
      if (!keepRootElement && element) {
        element.innerHTML = '';
      }
    }
  };
});

// Usage
const tableComponent = createDataTable({
  props: {
    columns: [
      { key: 'name', title: 'Name' },
      { key: 'email', title: 'Email' }
    ],
    data: [
      { name: 'Alice', email: 'alice@example.com' },
      { name: 'Bob', email: 'bob@example.com' }
    ]
  }
});

Stateful Pure Component

Even though called "pure", these components can maintain internal state:

const createCounter = pure(({ props }) => {
  let element;
  let count = props.initial || 0;
  
  return {
    mount(el) {
      element = el;
      this.render();
      
      // Add event listeners
      element.addEventListener('click', (e) => {
        if (e.target.matches('.increment')) {
          count++;
          this.render();
        } else if (e.target.matches('.decrement')) {
          count--;
          this.render();
        }
      });
    },
    
    update(context) {
      if (context && typeof context.count === 'number') {
        count = context.count;
        this.render();
      }
    },
    
    render() {
      element.innerHTML = `
        <div class="counter">
          <button class="decrement">-</button>
          <span class="count">${count}</span>
          <button class="increment">+</button>
        </div>
      `;
    },
    
    unmount(keepRootElement) {
      if (!keepRootElement && element) {
        element.innerHTML = '';
      }
    }
  };
});

Pure vs Regular Components

When to Use Pure Components

  • Simple presentational components without complex lifecycle needs
  • Performance-critical scenarios where you need minimal overhead
  • Custom rendering logic that doesn't fit the standard template system
  • Third-party integration where you need direct DOM control
  • Micro-components with very specific, limited functionality

When to Use Regular Components

  • Complex state management with lifecycle hooks
  • Template-based rendering with expression bindings
  • Parent-child communication through props and events
  • Plugin integration for cross-cutting concerns
  • Standard component patterns following Riot conventions

Types

interface RiotPureComponent<Context = object> {
  mount(element: HTMLElement, context?: Context): void;
  update(context?: Context): void;
  unmount(keepRootElement: boolean): void;
}

interface PureComponentFactoryFunction<
  InitialProps extends DefaultProps = DefaultProps,
  Context = any
> {
  ({
    slots,
    attributes,
    props,
  }: {
    slots?: TagSlotData<Context>[];
    attributes?: AttributeExpressionData<Context>[];
    props?: InitialProps;
  }): RiotPureComponent<Context>;
}

type DefaultProps = Record<PropertyKey, any>;

type TagSlotData<Context = any> = {
  id: string;
  html: string;
  bindings: BindingData<Context>[];
};

type AttributeExpressionData<Context = any> = {
  name: string;
  evaluate: (context?: Context) => any;
};

Install with Tessl CLI

npx tessl i tessl/npm-riot

docs

compilation.md

component-factory.md

component-registration.md

index.md

mounting-lifecycle.md

plugin-system.md

pure-components.md

utilities.md

tile.json