React Hooks
State.use, State.get, and providing context
The React adapter augments the State class with two static hooks, use and get. Import the adapter once, for its side effect, in your app's entry; everything else imports from @expressive/mvc.
import '@expressive/react';
import { State, Component } from '@expressive/mvc';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 once, on first render, without subscribing - the factory doesn't run again:
AppState.get((app) => {
analytics.track('page', app.page);
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.
Providing context
Hooks read what an ancestor provides. Provide a State with the bare Component class:
<Component for={Theme} color="dark">
<App />
</Component>- A class is constructed and owned by the element, destroyed on unmount;
isreceives it. - An instance - say one from
State.use()- is provided as-is and never destroyed by the element. - Other attributes are typed from the State and assigned to it on every render.
fallbackadds a Suspense boundary andcatchan error boundary; without them it adds none.- One State per element - several belong to a parent State owning them as fields.
Provider was removed in favor of <Component for>. Its record form (for={{ theme: Theme, auth: Auth }}) has no equivalent - compose a parent State instead. Details in the Context guide.
Reading context inline
Consumer was removed. Read context with State.get() in a small function component:
const Themed = () => {
const theme = Theme.get();
return <p style={{ color: theme.color }}>Themed</p>;
};See also
- Context guide -
<Component for>patterns and thegetinstruction. - Components - the
Componentclass as an alternative toState.use(), and rendering an instance as{instance}. - State - the base class and its methods.
- Utilities -
watchandlistenerfor observing state changes.