useScope
Runs a factory function exactly once per mount inside an effect scope. The factory’s return value is stable across re-renders. Reactive subscriptions registered inside the factory (via observe) are automatically cleaned up on unmount.
import { import observable
observable, import observe
observe, import onMount
onMount, import onUnmount
onUnmount, import useScope
useScope } from "@usels/web";
function function useCounter(): any
useCounter() { return import useScope
useScope(() => { const const count$: any
count$ = import observable
observable(0);
import onMount
onMount(() => { any
console.any
log("mounted"); });
import onUnmount
onUnmount(() => { any
console.any
log("unmounted"); });
return { count$: any
count$ }; });}import { observable, onMount, onUnmount } from "@usels/web";
function useCounter() { "use scope" const count$ = observable(0);
onMount(() => { console.log("mounted"); });
onUnmount(() => { console.log("unmounted"); });
return { count$ };}With props
Section titled “With props”Pass a second argument to receive reactive props inside the factory. p.field always returns the latest value without tracking. Use toObs(p) to get a reactive Observable<P> that updates when props change.
import { useScope, toObs, observe } from "@usels/web";
function useThemeSync(props: { theme: string }) { return useScope((p) => { // p.theme — raw latest (no reactive tracking) // obs$.theme.get() — reactive, triggers re-observation on change const obs$ = toObs(p);
observe(() => { document.documentElement.dataset.theme = obs$.theme.get(); });
return {}; }, props);}import { toObs, observe } from "@usels/web";
function useThemeSync(props: { theme: string }) { "use scope" const obs$ = toObs(props);
observe(() => { document.documentElement.dataset.theme = obs$.theme.get(); });
return {};}Lifecycle callbacks
Section titled “Lifecycle callbacks”| API | Timing | Notes |
|---|---|---|
onBeforeMount | useLayoutEffect — pre-paint | DOM measurement, scroll position restore |
onMount | useEffect — after mount | Returns optional cleanup function |
onUnmount | component unmount | Shorthand for onMount(() => cleanup) |
import { useScope, onBeforeMount, onMount, onUnmount } from "@usels/web";
useScope(() => { onBeforeMount(() => { // runs at useLayoutEffect timing — before paint });
onMount(() => { const sub = source$.onChange(handler); return () => sub(); // cleanup runs on unmount });
onUnmount(() => { resource.release(); // cleanup-only });});import { onBeforeMount, onMount, onUnmount } from "@usels/web";
function useExample() { "use scope"
onBeforeMount(() => { // runs at useLayoutEffect timing — before paint });
onMount(() => { const sub = source$.onChange(handler); return () => sub(); // cleanup runs on unmount });
onUnmount(() => { resource.release(); // cleanup-only });}Reactive subscriptions with observe
Section titled “Reactive subscriptions with observe”Use observe from @usels/web (not @legendapp/state) so subscriptions are automatically registered to the current scope and cleaned up on unmount.
import { useScope, observe } from "@usels/web";
useScope(() => { observe(() => { // re-runs whenever any accessed observable changes // automatically disposed when scope is destroyed document.title = title$.get(); });});import { observe } from "@usels/web";
function useTitle() { "use scope"
observe(() => { // re-runs whenever any accessed observable changes // automatically disposed when scope is destroyed document.title = title$.get(); });}toObs hints for non-plain fields
Section titled “toObs hints for non-plain fields”Pass a hints map as the second argument to toObs for fields that are opaque objects (DOM elements, class instances, Dates, etc.). Supported hints: 'opaque' and 'plain'.
Callback props do not need a hint — dispatch them via raw prop access (p.onClick?.(...)) so every call resolves to the latest closure.
import { useScope, toObs, observe } from "@usels/web";
function useEventHandler(props: { onClick: (e: MouseEvent) => void; data: SomeObject }) { return useScope((p) => { const obs$ = toObs(p, { data: "opaque", // prevents deep-proxying });
observe(() => { // raw-prop dispatch — always latest closure element.addEventListener("click", (e) => p.onClick?.(e)); });
return {}; }, props);}import { toObs, observe } from "@usels/web";
function useEventHandler(props: { onClick: (e: MouseEvent) => void; data: SomeObject }) { "use scope" const obs$ = toObs(props, { data: "opaque", // prevents deep-proxying });
observe(() => { element.addEventListener("click", (e) => props.onClick?.(e)); });
return {};}React Strict Mode
Section titled “React Strict Mode”In development with Strict Mode, React simulates unmount/remount to detect side-effect bugs. The factory may run twice per mount cycle. This is expected and safe — production always runs the factory once.
getStore() inside useScope
Section titled “getStore() inside useScope”When a component is rendered inside a StoreProvider, getStore() works inside a useScope factory without any additional setup.
import { createStore, observable, observe, useScope } from "@usels/web";
const [, getSettingsStore] = createStore("settings", () => { const theme$ = observable<"light" | "dark">("light"); return { theme$ };});
function useThemeSync() { return useScope(() => { const { theme$ } = getSettingsStore(); // resolves from nearest StoreProvider
observe(() => { document.documentElement.dataset.theme = theme$.get(); });
return {}; });}import { createStore, observable, observe } from "@usels/web";
const [, getSettingsStore] = createStore("settings", () => { const theme$ = observable<"light" | "dark">("light"); return { theme$ };});
function useThemeSync() { "use scope" const { theme$ } = getSettingsStore(); // resolves from nearest StoreProvider
observe(() => { document.documentElement.dataset.theme = theme$.get(); });
return {};}