Skip to Content
API

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. Use useObservablePromise.
  • Plain values with no stream involved — don’t wrap values in of() or a Subject just to use the hook; useState or 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, ): T

When 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 useObservablePromise and let <Suspense> own the fallback.
  • Wrapped in useDeferredValueuseDeferredValue(useSyncObservable(...)) is just useObservable, 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 the startWith, or use useObservable with the placeholder as initialValue.
  • 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 timeout or 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) => void

When not to use

  • Feeding state that components render — push into a Subject and read derived streams with useObservable / useSyncObservable instead; 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> }
Last updated on