Lightweight React adapters for the Observable primitives provided by @glowhop/observables. Wire your reactive pipelines into React without duplicating business logic.
- Concise hooks to project
Observable,ObservableList, andObservableMapdata into components. - Memoizable selection through an
accessorand an explicit dependency list. - Lazy concurrent variant (
useLazy) that mirrors emissions insidestartTransition. - Complete TypeScript typings with ESM and CommonJS builds under
dist/. - Zero extra runtime: hooks operate on the
@glowhop/observablesinstances you already own.
Ensure @glowhop/observables and react are already installed (peer dependencies).
npm install @glowhop/react-observables
# or
yarn add @glowhop/react-observables
# or
pnpm add @glowhop/react-observables
# or
bun add @glowhop/react-observablesimport { Observable } from "@glowhop/observables";
import { useValue } from "@glowhop/react-observables";
const counter = new Observable(0);
export function CounterButton() {
const clicks = useValue(counter);
return (
<button onClick={() => counter.set((prev) => prev + 1)}>
Clicks: {clicks}
</button>
);
}Hooks never create or manage observable instances; declare them in services, stores, or contexts and keep the reference stable (memoized, context value, singleton, etc.). React will resubscribe automatically.
- Returns the current snapshot exposed by the
Observableand updates synchronously on new emissions. accessorlets you derive a slice of the data (selector).depscontrols when the accessor is recomputed if it captures other React values.
const identity = useValue(user$); // entire object
const displayName = useValue(
user$,
(user) => `${user.firstName} ${user.lastName}`,
[], // optional deps if the accessor references other values
);- Same signature as
useValue, but updates are scheduled throughstartTransitionso React can prioritize urgent interactions. - Handy when projections are expensive or you want to avoid synchronous re-render spikes.
const preview = useLazy(
searchResults$,
(results) => results.slice(0, 5),
[],
);- Executes
accessorimmediately and on every emission. - Perfect for mirroring observable state into refs, services, or other non-React integrations.
const titleRef = useRef<HTMLTitleElement | null>(null);
useChange(
documentTitle$,
(value) => {
const node = titleRef.current;
if (node) {
node.textContent = value;
}
},
[titleRef],
);- Projects a single entry from an
ObservableList(by index) or anObservableMap(by key). - Optional
accessorlets you derive a value from the entry, even when it isundefined. - Automatically resubscribes when
index/keychanges.
const secondTodo = useEntry(todos$, 1);
const theme = useEntry(settings$, "theme", (entry) => entry ?? "light");- Stabilize observables with
useMemo, contexts, or singletons to avoid needless resubscriptions. - Compose observables in your domain layer, then expose only the streams components require.
- Lean on TypeScript generics provided by the package for end-to-end type safety.
bun install # install dependencies
bun test # run the test suite
bun run build # emit ESM/CJS bundles and .d.ts typesReleases are automated through semantic-release (see package.json's bun run release script).
Distributed under the ISC license.