Skip to content

use* vs create*

Most utilities come in two forms:

FormWhere to use it
use*React component or custom hook body
create*"use scope", useScope factory, store setup, or another lifecycle-owned factory

Use use* when you are writing regular React hook code:

import { useObservable } from "@usels/core";
import { useDebounced } from "@usels/core";
function Search() {
const draft$ = useObservable("");
const query$ = useDebounced(draft$, { ms: 150 });
return <input value={draft$.get()} onChange={(event) => draft$.set(event.currentTarget.value)} />;
}

The hook wrapper follows React’s hook rules and owns setup through React.

Use create* inside a scope or store setup:

import { createDebounced, observable } from "@usels/core";
function Search() {
"use scope";
const draft$ = observable("");
const query$ = createDebounced(draft$, { ms: 150 });
return <input value={draft$.get()} onChange={(event) => draft$.set(event.currentTarget.value)} />;
}

The scope owns the lifecycle, so the primitive does not need to be a React hook.

The same rule applies to DOM APIs:

import { createRef$ } from "@usels/core";
import { createElementSize } from "@usels/web";
function ResizablePanel() {
"use scope";
const el$ = createRef$<HTMLDivElement>();
const { width$, height$ } = createElementSize(el$);
return <div ref={el$}>{width$.get()} x {height$.get()}</div>;
}

In a regular hook body, use useRef$ and useElementSize instead.

Inside a scope, create* reads naturally because the scope already owns lifecycle. The primitive does not need hook rules — setup happens once per scope, cleanup happens when the scope tears down:

import { createDebounced, observable, observe } from "@usels/core";
function SearchBox({ onSearch }: { onSearch: (query: string) => void }) {
"use scope";
const draft$ = observable("");
const query$ = createDebounced(draft$, { ms: 150 });
observe(() => {
onSearch(query$.get());
});
return <input value={draft$.get()} onChange={(event) => draft$.set(event.currentTarget.value)} />;
}

Same component written with use* would need to be inside a component body with hook-rule constraints. Inside scope, create* keeps the factory side-effect-free from React’s perspective.

If React is the lifecycle boundary, use use*. If a use-legend scope or store is the lifecycle boundary, use create*.

createStore() is a store definition API, so it is the exception you define at module scope.

  • Scope & Lifecycle — the lifecycle model that makes create* safe without hook rules.
  • Effects API — the scope-level effect primitives paired with create*.
  • TypeScript — how create* and use* types relate in team-scale code.