@virentia/react API
Use @virentia/react at the rendering boundary. Keep domain logic in @virentia/core models.
ScopeProvider
Provides a core scope to hooks.
Use it once per React tree that should share state. Nested trees can provide another scope when they need isolation.
import { scope } from "@virentia/core";
import { ScopeProvider } from "@virentia/react";
const appScope = scope();
export function App() {
return (
<ScopeProvider scope={appScope}>
<Routes />
</ScopeProvider>
);
}useProvidedScope
Reads the provided scope.
Use it when a component needs to pass the scope into boundary helpers such as allSettled, caches, or external adapters.
function SaveButton({ saved }: { saved: EventCallable<void> }) {
const scope = useProvidedScope();
const onClick = () => allSettled(saved, { scope });
return <button onClick={onClick}>Save</button>;
}It throws when there is no ScopeProvider.
useUnit
Reads stores and binds callable units to the provided scope.
Use it for simple components that only need a few stores, events, or effects.
const countValue = useUnit(count);
const increment = useUnit(incremented);Object shape:
const model = useUnit({
count,
incremented,
pending: saveFx.$pending,
save: saveFx,
});Array shape:
const [countValue, increment] = useUnit([count, incremented]);useModel
Unwraps a model object.
Use it when a component works with a whole model and wants stores as values and events/effects as callbacks.
const model = useModel({
count,
incremented,
});Creates a model from props:
function createCounterModel({ props }: ModelContext<{ step: number }>) {
const clicked = event<void>();
const count = store(0);
reaction({
on: clicked,
run() {
count.value += props.step;
},
});
return { clicked, count };
}
function Counter(props: { step: number }) {
const model = useModel(createCounterModel, props);
const increment = () => model.clicked();
return <button onClick={increment}>{model.count}</button>;
}Use a cache when the model should survive unmount:
const model = useModel(createChatModel, props, {
cache: chatCache,
key: props.chatId,
});component
Pairs a model factory and a view.
Use it when the model belongs to the component lifecycle. It keeps creation, props, mount events, unmount events, and rendering in one pattern.
export const Counter = component({
model({ props }: ModelContext<{ step: number }>) {
const clicked = event<void>();
const count = store(0);
reaction({
on: clicked,
run() {
count.value += props.step;
},
});
return { clicked, count };
},
view({ model }) {
const increment = () => model.clicked();
return <button onClick={increment}>{model.count}</button>;
},
});Cached component:
export const ChatPanel = component({
cache: chatCache,
key: (props: { chatId: string }) => props.chatId,
model: createChatModel,
view({ model }) {
return <div>{model.messages.items.length}</div>;
},
});createModelCache
Creates a scope-aware cache keyed by your ID.
Use it when a model should survive unmount and be reused later by key: chats, tabs, detail screens, media players, previews.
const chatCache = createModelCache<string, ChatProps, ChatModel>();Read cached models:
chatCache.has("support", appScope);
chatCache.get("support", appScope);
chatCache.getInstance("support", appScope);Dispose cached models:
chatCache.delete("support", appScope);
chatCache.clear(appScope);ModelContext
Model factories receive:
Use this context when model logic depends on props, lifecycle, the current scope, or a cache key.
interface ModelContext<Props, Key = undefined> {
readonly scope: Scope;
readonly owner: Owner;
readonly props: StoreWritable<Props>;
readonly mounted: EventCallable<void>;
readonly unmounted: EventCallable<void>;
readonly mounts: StoreWritable<number>;
readonly key: Key;
}Use mounted, unmounted, and mounts for lifecycle logic inside the model.