Expressive MVC
API Reference

Component

The Component class - every method, prop, and lifecycle hook

Component extends State and works directly as a React component. It's a real React class component under the hood, so it integrates with error boundaries, devtools, refs, and the React tree.

import { Component } from '@expressive/react';

class Counter extends Component {
  count = 0;

  render() {
    return <button onClick={() => this.count++}>{this.count}</button>;
  }
}

<Counter />;

Instance properties

this.props

Readonly. Every prop passed to the JSX element - state-derived props, render-declared props, and special props (is, fallback, children).

props is itself observable: this.set('props', callback) fires whenever a render delivers new props, and effects reading this.props re-run with it.

this.fallback

ReactNode | false. Content displayed while the component or its children are suspended, and during error recovery. Can be set as a class field, updated inside catch(), or overridden by the JSX fallback prop.

Defaults to null - nothing renders while suspended. Set false to opt out of the component's own Suspense boundary entirely; suspension then bubbles to the nearest ancestor boundary.


Instance methods

render(props?)

Override to define output. Properties accessed via this in the render body are reactive - changes trigger a re-render.

Accept an optional parameter to declare extra props beyond state fields:

class Card extends Component {
  title = '';

  render(props = {} as { className: string }) {
    return <div className={props.className}>{this.title}</div>;
  }
}
  • The = {} as T default satisfies TypeScript's JSX inference and is safely ignored at runtime.
  • Non-optional parameter fields become required JSX attributes.
  • Without a parameter (or without render() altogether), children pass through a context provider.

When a subclass overrides render(), it composes with the base rather than replacing it. Each render up the prototype chain wraps the one below as props.children, base-outermost - no super.render() call. A wrapper that omits props.children drops the inner content (the getter is lazy, so it never runs). See Render composition.

The whole composed chain runs in a single host render. Prefer this for reactivity over React hooks inside render(), and if you do use hooks, beware the sharp edge: a hook in a layer below a wrapper that conditionally renders props.children breaks the rules of hooks. For a scope isolated from that, use a subcomponent.

catch(error) optional

class SafeView extends Component {
  async catch(error: Error) {
    this.fallback = <p>Recovering...</p>;
    await reportError(error);
  }

  render() {
    return <Risky />;
  }
}

Error boundary handler. Called when a child throws during render. While catch() is pending, this.fallback is displayed; when it resolves, render() is retried.

  • Setting this.fallback inside catch() gives error-specific UI (reverted after recovery).
  • Rejecting from catch() propagates the error to the next parent boundary.
  • Without catch(), errors propagate automatically.
  • Sync catch() triggers immediate retry.

Lifecycle hooks

Component inherits the full State lifecycle. See State for details.

  • new() - one-shot setup, return cleanup.
  • catch(error) - error boundary, above.
  • Destruction runs on unmount or this.set(null).

A use() method never runs for a Component rendered as JSX - it belongs to the State.use() hook path only.


JSX props

Settable state fields become optional JSX props, applied to the instance on every render:

<Counter count={5} />

A field passed on one render and omitted on the next is cleared - reset to undefined, not left at its previous value. Props are a live binding, not one-time seeds.

Special props

Every Component also accepts these, regardless of state fields:

PropTypeDescription
is(instance: T) => voidCalled once with the created instance
refRef<T>Standard React ref - the instance after mount, null on unmount
fallbackReactNode | falseOverrides this.fallback; false defers to an ancestor boundary
childrenReactNodePassed through unless render() is declared without a parameter

is runs after props apply but before the new() lifecycle hook - so it may configure state that new() then observes. Use ref (object or callback, per React convention) for post-mount imperative access instead.


Subcomponents

Any method whose name starts with a capital letter becomes a React component scoped to this:

class Dashboard extends Component {
  items: string[] = [];

  Sidebar(props: { label: string }) {
    return (
      <aside>
        <h2>{props.label}</h2>
        <ul>
          {this.items.map((i) => (
            <li key={i}>{i}</li>
          ))}
        </ul>
      </aside>
    );
  }

  render() {
    return <this.Sidebar label="Items" />;
  }
}
  • Each usage subscribes independently to the parent instance.
  • Accept props like any React component.
  • Accessible via Dashboard.get() then <dashboard.Sidebar />.

Overriding a subcomponent

A subclass swaps one out by assigning an instance field - the function receives the live instance as this:

function CustomSidebar(this: MyDashboard) {
  return <aside>{this.items.length} items</aside>;
}

class MyDashboard extends Dashboard {
  Sidebar = CustomSidebar;
}

Assignment also works at runtime (instance.Sidebar = fn), which triggers a refresh wherever it's rendered. This makes a behavior-complete base with placeholder subcomponents a plug-and-play kit for subclasses.


TypeScript shape

declare class Component extends State {
  readonly props: Component.Props<this>;
  fallback: Component.Node;

  render(props?: {}): Component.Node;
  catch?(error: Error): Promise<void> | void;
}

Props are inferred automatically from state fields plus any render(props) declaration.

Component.Node is the host's element type - ReactNode once @expressive/react is loaded. In a host-agnostic package (one importing only @expressive/mvc), it falls back to any so un-annotated render overrides still type-check as JSX; annotate returns as Component.Node in library code meant to run under any adapter.


See also

  • Components guide - patterns, subclass composition, children, context.
  • State - the base class.
  • Hooks - State.use, State.get, Provider, Consumer.

On this page