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. 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
}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.
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,
): TExample
import type {ChangeEvent} from 'react'
import {useObservableEvent, useSyncObservable} from 'react-rx'
import {map, Subject, tap, type Observable} from 'rxjs'
const text$ = new Subject<string>()
function SearchField() {
const handleChange = useObservableEvent((events$: Observable<ChangeEvent<HTMLInputElement>>) =>
events$.pipe(
map((e) => e.currentTarget.value),
tap((value) => text$.next(value)),
),
)
// 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.
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 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.target.value)}
min={1}
max={10}
/>
<div>Value is: {value}</div>
</>
)
}
function nonNullable<T>(v: T): v is NonNullable<T> {
return v != null
}