Expressive State
API Reference

React Hooks

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

The React adapter 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, { Provider, Consumer } from '@expressive/react';

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: 'a@b.c' });

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 - use it to bridge React hooks:

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

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 if the state is not provided above.
  • Re-subscribes if the upstream instance is replaced.

Optional lookup

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

Required values

const user = User.get(true); // Required<User> - suspends on undefined fields

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>(promise: Promise<T>): Promise<T>;
  <T>(fn: () => Promise<T>): Promise<T>;
};
const data = DataService.get((svc, refresh) => {
  const reload = () => refresh(svc.fetch());
  return { items: svc.items, reload };
});

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.

On this page