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: '[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. AComponentrendered as JSX never calls ause()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 | undefinedRequired 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
ForceRefreshfunction.
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
| Prop | Type | Description |
|---|---|---|
for | State | 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 |
fallback | ReactNode | Wraps children in a Suspense boundary |
name | string | Debug label for the boundary |
children | ReactNode | Content rendered within the provider |
[field] | varies | State 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 -
Providerpatterns and thegetinstruction. - Components - the
Componentclass as an alternative toProvider+State.use(). - State - the base class and its methods.
- Utilities -
watchandlistenerfor observing state changes.