Expressive MVC
API Reference

React Hooks

use, State.use, State.get, Provider, and Consumer

The React adapter exports a use hook, augments the State class with two static methods (use and get), and provides two components (Provider and Consumer). These are the React-specific entry points to Expressive.

import State, { use, Provider, Consumer } from '@expressive/react';

use(subject)

Subscribes a React component to an existing observable object or State instance.

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

function CounterView({ counter }: { counter: Counter }) {
  const { count, increment } = use(counter);
  return <button onClick={increment}>{count}</button>;
}
  • Returns a tracking proxy on the initial render; reading properties subscribes the component.
  • Re-renders only when accessed properties change.
  • Re-subscribes if subject is replaced.
  • Activates an unready observable instance, but does not own its lifecycle or destroy it on unmount.
  • Throws if subject is not observable. A destroyed observable does not throw - the hook renders its last-known values and no further updates arrive.
  • Works with any observable, not just State - plain classes can opt in via the observable protocol.

Use this when the state comes from somewhere else - an externally-owned store, a module-scope instance, a prop. That also makes it the natural bridge when migrating from hooks: keep your store where it is, subscribe with use. State.use() is for when the component should create and own the instance. State.get() is for finding one in context.


State.use(...args)

Creates a state instance scoped to a React component.

function CounterView() {
  const { count, increment } = Counter.use();
  return <button onClick={increment}>{count}</button>;
}
  • Instance is created on mount and destroyed on unmount.
  • Reading properties subscribes the component - it re-renders when those properties change.
  • Methods are auto-bound; safe to destructure and pass as event handlers.
  • Strict mode safe (handles double-mount correctly).

With initial values

const form = Form.use({ name: 'Alice', email: '[email protected]' });

With a lifecycle callback

const timer = Timer.use((self) => {
  self.start();
  return () => self.stop();
});

With a use() method

If your class defines a use() method, its parameters define the arguments to State.use():

class Search extends State {
  query = '';

  use(props: { initialQuery: string }) {
    this.query = props.initialQuery;
  }
}

const state = Search.use({ initialQuery: 'react' });

use() runs on every render - handy for bridging React hooks:

class Nav extends State {
  use() {
    const navigate = useNavigate();
    if (this.shouldRedirect) navigate('/home');
  }
}

This applies to the State.use() hook path only. A Component rendered as JSX never calls a use() method.


State.get()

Fetches a state instance from context and subscribes to accessed properties.

function Profile() {
  const { user } = UserProfile.get();
  return <h1>{user.name}</h1>;
}
  • Throws Could not find UserProfile in context. if the state is not provided above.
  • Re-subscribes (and refreshes) if the upstream instance is replaced.

Optional lookup

const maybe = Theme.get(false); // Theme | undefined

Required values

const user = User.get(true); // Required<User>

With true, reading a property that is currently undefined throws a plain Error - User-XXXXXX.name is required in this context. That surfaces at the nearest error boundary, not Suspense. Use it when the component's contract is "this data exists by now."

Computed selector

Pass a factory to derive a value. The component re-renders only when the derived result changes by ===:

const summary = Cart.get((cart) => ({
  total: cart.total,
  count: cart.count
}));

The factory receives:

  • A tracking proxy (reads subscribe).
  • A ForceRefresh function.

Effect mode

Return null from the factory to run a side effect without subscribing:

AppState.get((app) => {
  console.log('user changed:', app.user);
  return null;
});

ForceRefresh

The second argument is a function that forces the component to re-render:

type ForceRefresh = {
  (): void;
  <T>(waitFor: Promise<T>): Promise<T>;
  <T>(invoke: () => Promise<T>): Promise<T>;
};
const data = DataService.get((svc, refresh) => {
  const reload = () => refresh(svc.fetch());
  return { items: svc.items, reload };
});

Given a promise (or async function), it refreshes now and again when the promise settles.


Provider

Puts a state instance (or several) into React context for its descendants.

<Provider for={Theme}>
  <App />
</Provider>

Props

PropTypeDescription
forState | State.Type | Record<string, State.Type>State instance, class, or map of classes to provide
is(instance) => void | (() => void)Called per created instance; optional cleanup
fallbackReactNodeWraps children in a Suspense boundary
namestringDebug label for the boundary
childrenReactNodeContent rendered within the provider
[field]variesState fields passed as JSX props, merged to instance

Providing a class

<Provider for={Theme} color="dark">
  <App />
</Provider>

The provider creates an instance on mount and destroys it on unmount.

Providing an instance

const theme = Theme.new();
<Provider for={theme}>
  <App />
</Provider>;

Externally-owned instances are not destroyed when the provider unmounts.

Providing multiple

<Provider for={{ theme: Theme, auth: AuthService }}>
  <App />
</Provider>

With a creation callback

<Provider for={Theme} is={(theme) => theme.load()}>
  <App />
</Provider>

For multi-state providers, the is callback runs once per created instance and can return a cleanup function.

With Suspense

<Provider for={UserProfile} fallback={<Spinner />}>
  <ProfileView />
</Provider>

Consumer

Render-prop access to context state.

<Consumer for={Theme}>
  {(theme) => <p style={{ color: theme.color }}>Themed</p>}
</Consumer>

The child function receives a tracking proxy, so property reads subscribe just like State.get().


See also

  • Context guide - Provider patterns and the get instruction.
  • Components - the Component class as an alternative to Provider + use().
  • State - the base class and its methods.
  • Utilities - watch, listener, and the observable protocol behind use(subject).

On this page