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
subjectis replaced. - Activates an unready observable instance, but does not destroy it on unmount.
- Throws if
subjectis not observable or has already been destroyed.
Use this when state or observable object comes from somewhere else.
State.use() is for when the component should create and own instance.
State.get() is for finding from 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 - 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 | undefinedRequired values
const user = User.get(true); // Required<User> - suspends on undefined fieldsComputed 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>(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
| 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+use(). - State - the base class and its methods.