React hooks
useObservable()
A React hook that returns the current/latest value from an observable. Store updates are deferred by default via useDeferredValue: urgent renders keep the previous value while a background render catches up. That makes it safe to suspend on the returned value without replacing already-revealed UI with a Suspense fallback.
The deferral is identity-coherent: unlike a bare useDeferredValue(useObservable(...)), the observable identity and its value are deferred as one snapshot, and when the observable identity changes (e.g. it is memoized on a document id that just changed) the hook falls back to the live value — typically the new observable’s synchronous emission or the initialValue — so the previous identity’s value never renders under the new one.
Mounts, remounts, and <Activity> reveals still render the current snapshot synchronously (no initial-value flash). On the server, this hook renders exactly what the client’s first paint will show (a synchronous emission when there is one, else the resolved initialValue, else nothing) and never throws for a missing initialValue.
Prefer this hook for previews, validation, lists, and other non-input reads.
Signature
function useObservable<T>(observable$: Observable<T>): T | undefined
function useObservable<T>(
observable$: Observable<T>,
initialValue: T | (() => T),
options?: UseObservableOptions,
): T
interface UseObservableOptions {
disabled?: boolean
}disabled pauses the live subscription (later emissions stop updating the component; the last value is kept). It does not skip the render-phase warm-up subscription — see the guide for swapping the observable when you need zero subscriptions.
When not to use
- Controlled inputs, or values read back in the same event — deferred updates can lag the caret or drop keystrokes under load. Use
useSyncObservable. - One-shot async data where “loading” is a Suspense fallback — emitting
{loading: true}placeholder values duplicates what<Suspense>already expresses. UseuseObservablePromise. - Plain values with no stream involved — don’t wrap values in
of()or aSubjectjust to use the hook;useStateor props are simpler and faster. - Observables created fresh on every render — the hook caches by reference identity, so an unstable observable resubscribes every render. Memoize it, hoist it, or rely on the React Compiler — see keep observables referentially stable.
Example
import {useMemo} from 'react'
import {useObservable} from 'react-rx'
import {interval} from 'rxjs'
function MyComponent() {
const observable = useMemo(() => interval(100), [])
const number = useObservable(observable, 0)
return <>The number is {number}</>
}useSyncObservable()
A React hook that returns the current/latest value from an observable synchronously via useSyncExternalStore. This is the v4 useObservable behavior.
Use it when the value feeds a controlled input (or must stay consistent within the same event), or when you need strict control over server markup: the server renders the resolved initialValue and throws without one.
Signature
function useSyncObservable<T>(observable$: Observable<T>): T | undefined
function useSyncObservable<T>(
observable$: Observable<T>,
initialValue: T | (() => T),
options?: UseObservableOptions,
): TWhen not to use
- As the default for lists, previews, and other chrome — synchronous store mutations cannot be marked as Transitions, so a suspending child replaces already-visible content with the nearest Suspense fallback (see the useSyncExternalStore caveats and compare both hooks in the Suspense example). Use
useObservable. - Fetch-loading UI — use
useObservablePromiseand let<Suspense>own the fallback. - Wrapped in
useDeferredValue—useDeferredValue(useSyncObservable(...))is justuseObservable, minus its identity-coherence guarantee.
Example
import {useState} from 'react'
import {useSyncObservable} from 'react-rx'
import {Subject} from 'rxjs'
function SearchField() {
const [text$] = useState(() => new Subject<string>())
// Controlled input values must update synchronously.
const text = useSyncObservable(text$, '')
return <input value={text} onChange={(e) => text$.next(e.currentTarget.value)} />
}useObservablePromise()
A React hook that turns an observable into a use()-compatible promise for Suspense and Activity pre-rendering.
Signature
function useObservablePromise<T>(
observable: Observable<T>,
options?: UseObservablePromiseOptions,
): ObservablePromise<T>
interface UseObservablePromiseOptions {
disabled?: boolean
ttl?: number
}
type ObservablePromise<T> = Promise<T> &
({status: 'pending'} | {status: 'fulfilled'; value: T} | {status: 'rejected'; reason: unknown})The hook does not suspend. Pass the returned promise to React’s use inside a <Suspense> boundary. Suspends until the first emission; later emissions update without re-suspending. Errors reject the promise (Error Boundary). See the guide for startWith caveats, disabled / ttl, and when to prefer useObservable.
When not to use
- Streams that
startWith(...)a placeholder — the placeholder is the first emission, so the promise fulfills instantly with it and Suspense never shows. Drop thestartWith, or useuseObservablewith the placeholder asinitialValue. - Live values that should render immediately without a boundary — use
useObservable. - Controlled inputs — use
useSyncObservable. - Unstable observable identity — every new observable reference is a new pending promise, which re-triggers the fallback. Keep the observable stable, and prefer creating the promise in a parent that does not itself suspend.
Example
import {Suspense, use, useMemo} from 'react'
import {useObservablePromise} from 'react-rx'
import {fromFetch} from 'rxjs/fetch'
function Profile({url}: {url: string}) {
const data$ = useMemo(() => fromFetch(url, {selector: (r) => r.json()}), [url])
const promise = useObservablePromise(data$)
return (
<Suspense fallback="Loading…">
<Pre promise={promise} />
</Suspense>
)
}
function Pre({promise}: {promise: Promise<unknown>}) {
return <pre>{JSON.stringify(use(promise), null, 2)}</pre>
}preloadObservablePromise()
Warm the useObservablePromise cache outside of rendering (for example on mouseenter or in a route loader). Not a hook — callable anywhere. Returns the same promise instance the hook would return for that observable.
Calling it starts the source subscription immediately. Pending entries are never timed out, so a never-emitting / hung observable keeps both the promise and the subscription alive until it settles. Prefer RxJS timeout (or cancel the source) when a preload can stall.
Signature
function preloadObservablePromise<T>(
observable: Observable<T>,
options?: {ttl?: number},
): ObservablePromise<T>Default ttl is 5000 (longer than the hook default) so a hover-warmed value survives until click/navigation.
When not to use
- Sources that may never settle without bounding them — the subscription stays alive until the promise settles. Add a
timeoutor make the source cancellable first. - As a data-reading mechanism — it only warms the cache. Components still read through
useObservablePromise+use().
useObservableEvent()
A React hook that turns an event handler into an observable stream. Pass a function that receives an observable of events and returns an observable of side effects; the hook returns a stable callback you can attach to DOM or component event props. When the returned callback is invoked, its argument is emitted into the pipeline, which is subscribed for the lifetime of the component.
Prefer an explicit Subject instead. useObservableEvent hides both the subscription and the data flow inside the hook: values vanish into tap side effects, and the pipeline cannot be composed with anything else. The same wiring is clearer when events push into a Subject you can see, and behavior lives on streams derived from it — see working with events in the guide. Reserve this hook for pipelines that are genuinely event-first, per-component, and side-effect-only.
Signature
function useObservableEvent<T, U>(
handleEvent: (arg: Observable<T>) => Observable<U>,
): (arg: T) => voidWhen not to use
- Feeding state that components render — push into a
Subjectand read derived streams withuseObservable/useSyncObservableinstead; the flow stays visible and testable. - Anything another stream needs to compose with — the internal subject is unreachable from outside the hook.
- New code, as a default — reach for the explicit pattern first.
Example — and the explicit-Subject equivalent to prefer:
import {useMemo, useState} from 'react'
import {useObservable, useObservableEvent} from 'react-rx'
import {scan, Subject, tap} from 'rxjs'
// With useObservableEvent: the pipeline is subscribed invisibly, for side effects
function WithHook() {
const [count, setCount] = useState(0)
const handleClick = useObservableEvent((clicks$) =>
clicks$.pipe(
scan((count) => count + 1, 0),
tap(setCount),
),
)
return <button onClick={handleClick}>Clicked {count} times</button>
}
// Preferred: events push into a Subject; hooks read the derived stream
function WithSubject() {
const [clicks$] = useState(() => new Subject<void>())
const count = useObservable(
useMemo(() => clicks$.pipe(scan((count) => count + 1, 0)), [clicks$]),
0,
)
return <button onClick={() => clicks$.next()}>Clicked {count} times</button>
}