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 once a value has been emitted). When no initialValue is given, the hook briefly subscribes during render so a synchronous emission (e.g. from startWith) is available from the very first render; with an initialValue the observable is not subscribed during render — the initialValue paints first and the live subscription starts on commit, keeping subscribe-time side effects out of the render phase. Once the hook has received an emission, replacement observables on later renders are warmed during render again — that is what lets components that rebuild the observable on every render settle instead of re-rendering forever; before the first emission (and always while disabled), identity churn stays subscription-free. On the server, this hook renders exactly what the client’s first paint will show (the resolved initialValue when one is provided, else a synchronous emission when there is one, else nothing) and never throws for a missing initialValue.
Prefer this hook for previews, validation, lists, and other non-input reads. Use useSyncObservable for controlled inputs or strict SSR control.
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
}The overload without initialValue is deprecated. v7 removes it and requires the argument.
useObservable(observable$, undefined) is a drop-in replacement with the same type and, in v6,
the same behavior. See the v6 to v7 migration
guide.
disabled pauses the live subscription (later emissions stop updating the component; the last value is kept). When no initialValue is given the render-phase warm-up subscription still runs; with an initialValue there is no warm-up while disabled, so disabled: true means zero subscriptions — see the guide.
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.
Caveat: store mutations cannot be marked as Transitions. Suspending on a value returned by this hook replaces already-visible content with the nearest Suspense fallback — see the useSyncExternalStore caveats . Compare the two hooks in the Suspense example.
Signature
function useSyncObservable<T>(observable$: Observable<T>): T | undefined
function useSyncObservable<T>(
observable$: Observable<T>,
initialValue: T | (() => T),
options?: UseObservableOptions,
): TThe overload without initialValue is deprecated. v7 removes it and requires the argument.
useSyncObservable(observable$, undefined) is a drop-in replacement with the same type and, in
v6, the same behavior. See the v6 to v7 migration
guide.
Example
import type {ChangeEvent} from 'react'
import {useMemo} from 'react'
import {useObservableSubject, useSyncObservable} from 'react-rx'
import {map} from 'rxjs'
function SearchField() {
const [changes$, handleChange] = useObservableSubject<ChangeEvent<HTMLInputElement>>()
const text$ = useMemo(() => changes$.pipe(map((event) => event.currentTarget.value)), [changes$])
// Controlled input values must update synchronously.
const text = useSyncObservable(text$, '')
return <input value={text} onChange={handleChange} />
}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.
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.
useObservableSubject()
Creates an RxJS Subject scoped to the component instance and returns its two halves: an observable of the values pushed into it, plus a stable handler that pushes them.
Only the observable side of the Subject is exposed, so the pipeline cannot accidentally push into the stream and the handler cannot be subscribed. The handler is referentially stable, so it can be passed straight to event props or memoized children without useCallback. Read the observable with useObservable / useSyncObservable when the pipeline produces something to render, or subscribe it in an effect for side-effect-only pipelines. Values emitted while nothing is subscribed are dropped, exactly like a Subject.
This is the building block useObservableEvent is made of, and the recommended replacement for it: useObservableEvent is removed in v7 while useObservableSubject carries over unchanged, so call sites migrated to it need no further changes. See the v6 → v7 migration guide.
Signature
function useObservableSubject<T>(): [events$: Observable<T>, handleEvent: (event: T) => void]Example
import {useMemo} from 'react'
import {useObservableSubject, useSyncObservable} from 'react-rx'
import {map} from 'rxjs'
const ShowSliderValue = () => {
const [input$, handleChange] = useObservableSubject<string>()
const value$ = useMemo(() => input$.pipe(map((value) => Number(value))), [input$])
// The derived stream is the state — no setState mirror needed.
const value = useSyncObservable(value$, 1)
return (
<>
<input
type="range"
value={value}
onChange={(event) => handleChange(event.currentTarget.value)}
min={1}
max={10}
/>
<div>Value is: {value}</div>
</>
)
}Side-effect-only pipelines (analytics, persistence, …) subscribe the observable in an effect instead:
import {useEffect} from 'react'
import {useObservableSubject} from 'react-rx'
import {concatMap} from 'rxjs'
function SaveSearchButton({term}: {term: string}) {
const [saves$, handleSave] = useObservableSubject<string>()
useEffect(() => {
const subscription = saves$.pipe(concatMap((t) => saveSearch(t))).subscribe()
return () => subscription.unsubscribe()
}, [saves$])
return <button onClick={() => handleSave(term)}>Save search</button>
}useObservableEvent()
Deprecated. v7 removes useObservableEvent. Use useObservableSubject
instead, as shown in Handling events. See the v6 to v7 migration
guide.
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 single argument is emitted into the observable. The pipeline you return is subscribed for the lifetime of the component, and unsubscribed on unmount.
Signature
function useObservableEvent<T, U>(
handleEvent: (arg: Observable<T>) => Observable<U>,
): (arg: T) => voidExample
import {useState} from 'react'
import {useObservableEvent} from 'react-rx'
import {filter, map, tap} from 'rxjs'
const ShowSliderValue = () => {
const [value, setValue] = useState(1)
const handleChange = useObservableEvent((value$) =>
value$.pipe(
// Ignore nullish values
filter(nonNullable),
// Cast to number
map((value) => Number(value)),
// Update local state
tap(setValue),
),
)
return (
<>
<input
type="range"
value={value}
onChange={(event) => handleChange(event.currentTarget.value)}
min={1}
max={10}
/>
<div>Value is: {value}</div>
</>
)
}
function nonNullable<T>(v: T): v is NonNullable<T> {
return v != null
}