npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@expressive/react

v0.82.0

Published

Class-based reactive state for React. Define models as plain classes; components update when the values they read change.

Readme


The React adapter for Expressive MVC. Define state as a class, use it in a component, and reads automatically subscribe - components re-render only when accessed values change.

npm install @expressive/react react
import { State } from '@expressive/react';

class Counter extends State {
  count = 0;
  increment = () => this.count++;
  decrement = () => this.count--;
}

function CounterWidget() {
  const { count, increment, decrement } = Counter.use();

  return (
    <div>
      <button onClick={decrement}>-</button>
      <span>{count}</span>
      <button onClick={increment}>+</button>
    </div>
  );
}

Component

Component is a State that owns its own rendering - a persistent class instance with lifecycle, context, suspense, and error handling baked in. Reach for it when state is intrinsic to a rendered unit: form controls, layout shells, route controllers, media players.

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

class Counter extends Component {
  count = 0;
  render() {
    return <button onClick={() => this.count++}>{this.count}</button>;
  }
}

<Counter />;

Anything read through this in render() is reactive; the instance survives across renders, so refs, sockets, and Maps held on this persist. State fields also become optional JSX props (<Counter count={5} />).

Render composition

When a subclass overrides render(), it composes with its base rather than replacing it - no super.render(). Each render() up the prototype chain wraps the one below, base-outermost, with the inner output arriving as props.children.

class Frame extends Component {
  render(props = {} as { children?: React.ReactNode }) {
    return (
      <section className="frame">
        <header>Frame</header>
        {props.children}
      </section>
    );
  }
}

class Page extends Frame {
  body = 'Hello';
  render() {
    return <p>{this.body}</p>;
  }
}

// <Page /> -> <section class="frame"><header>Frame</header><p>Hello</p></section>

Every layer binds to the same live instance, so all read the same reactive this. A base that wraps must declare a props parameter and render props.children - a layer that ignores it silently drops everything below. (A base can also choose to defer to a subclass's render, e.g. a leaf <a> that's overridable; the base detects the subclass content by identity and returns it as-is.)

Subcomponents

PascalCase members become their own reactive React components scoped to the live instance. They read the parent's reactive this directly - no props plumbing - yet each is an isolated component with its own hooks and boundary. Subclasses override them to customize pieces without touching behavior.

abstract class Toggle extends Component {
  active = false;

  toggle = () => {
    this.active = !this.active;
  };

  Active() { return null; }       // subclasses fill these in
  Inactive() { return null; }

  render() {
    return (
      <div onClick={this.toggle}>
        {this.active ? <this.Active /> : <this.Inactive />}
      </div>
    );
  }
}

class DarkModeSwitch extends Toggle {
  Active() { return <span>Dark</span>; }
  Inactive() { return <span>Light</span>; }
}

The base owns behavior and structure; subclasses author only the rendering. Override a subcomponent as a method when it takes no props, or as an arrow field when you want its props type inferred from the base - which is how @expressive/router's NavLinks exposes its overridable Item / List / Group members.

Self-providing context

A Component provides itself to context automatically - no Provider needed. Both its own render output and any children passed in can read it with get().

class Tabs extends Component {
  active = 0;
  render(props = {} as { children?: React.ReactNode }) {
    return <div className="tabs">{props.children}</div>;
  }
}

function Tab({ index, label }: { index: number; label: string }) {
  const tabs = Tabs.get();              // reads the enclosing Tabs instance
  return (
    <button
      className={tabs.active === index ? 'on' : undefined}
      onClick={() => (tabs.active = index)}>
      {label}
    </button>
  );
}

<Tabs>
  <Tab index={0} label="One" />
  <Tab index={1} label="Two" />
</Tabs>;

It works the same whether the consumer is part of the Component's own render or is passed in as children - both sit under the instance's context. A render-less Component still self-provides, which is what makes it useful purely as a context/boundary placement.

Props

State fields become optional JSX props, merged onto the instance every render - passing count={5} is the same as assigning this.count = 5. Extra props beyond fields are read through a parameter on render:

class Greeting extends Component {
  name = 'World';                                  // -> optional `name` prop

  render(props = {} as { loud?: boolean }) {       // extra render prop
    return <h1>Hello {this.name}{props.loud && '!!!'}</h1>;
  }
}

<Greeting name="React" loud />;

Every Component also accepts three special props:

  • is - callback handed the instance once, at creation.
  • ref - standard React ref; set after mount, cleared on unmount.
  • fallback - Suspense/error UI, overriding the instance's own.

Suspense

A value that isn't ready yet suspends the render; fallback shows in the meantime.

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

class Profile extends Component {
  user = set<User>();              // undefined until set - suspends render
  fallback = <Spinner />;

  render() {
    return <span>{this.user.name}</span>;
  }
}

Shared state via context

Provide a model once; descendants read it with State.get() - no props, no selectors.

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

class Session extends State {
  user = 'guest';
}

function App() {
  return (
    <Provider for={Session}>
      <Profile />
    </Provider>
  );
}

function Profile() {
  const { user } = Session.get();   // nearest Session in context
  return <p>Signed in as {user}</p>;
}

At a glance

  • State.use() - create an instance bound to a component's lifecycle.
  • State.get() - read shared state from context, no prop drilling.
  • Provider / Consumer - explicit hierarchical dependency injection.
  • Component - smart controls and shells whose behavior lives in the tree.
  • Suspense & error boundaries - async values suspend; catch() handles child errors.

Full guide and API reference → github.com/gabeklein/expressive-mvc

License

MIT