# Core
# useObservable
useObservable<TOutput>(init: function): Observable<TOutput>
useObservable<TOutput, TInputs>(
init: function,
inputs: TInputs
): Observable<TOutput>
React functional components are called many times during their lifecycle. Create or transform Observables with useObservable so that the operations will not be repeatedly performed.
Accepts an epic-like function that returns an Observable. Optionally accepts an array of dependencies which will be turned into Observable and be passed to the epic function.
Gotcha
It is not safe to access other variables from closure directly in the init function. See Gotchas.
Restriction
The dependencies array is internally passed to useEffect (opens new window). So same restriction is applied. The length of the dependencies array must be fixed.
Type parameters:
TOutputOutput value within Observable.TInputsA readonly tuple of all dependencies.
Parameters:
| Name | Type | Description |
|---|---|---|
init | (inputs$: Observable<TInputs>): Observable<TOutput> | A pure function that, when applied to an Observable, returns an Observable. |
inputs | TInputs | An optional dependency array with fixed length. When one of the dependencies changes the Observable in init will emit an array of all the dependencies. |
Returns:
Observable<TOutput> Always the same Observable.
Examples:
interface CompProps {
isOpen: boolean
}
const Comp: React.FC<CompProps> = props => {
const [showPanel, setShowPanel] = useState(false)
// Listen to props or state change
const enhanced$ = useObservable(
inputs$ => inputs$.pipe(map(([isOpen, showPanel]) => isOpen && showPanel)),
[props.isOpen, showPanel]
)
}
Create Observable:
const now$ = useObservable(
() => interval(1000).pipe(
map(() => new Date().toLocaleString())
)
)
Transform Observables:
// outers$ are created from other React-unrelated module
const enhanced$ = useObservable(() => outers$.pipe(mapTo(false)))
You can even mix them all together:
const enhanced$ = useObservable(
inputs$ => isEven$.pipe(
withLatestFrom(inputs$),
map(([isEven, [isOpen]]) => isEven && isOpen)
),
[props.isOpen]
)
# useLayoutObservable
If no dependencies provided it is identical with useObservable. Otherwise it uses useLayoutEffect (opens new window) to listen props and state changes.
This is useful if you need values before next paint.
Use it scarcely as it runs synchronously before browser paint. Too many synchronous emissions from the observable could stretch the commit phase.
# useObservableCallback
useObservableCallback<TOutput, TInput, TParams>(
init?: function,
selector?: undefined | function
): [function, Observable<TOutput>]
Returns a callback function and an events Observable.
Whenever the callback is called, the Observable will emit the first argument of the callback.
v2.1.0 From v2.1.0 optionally accepts a selector function which transforms an array of event arguments into a single value.
TIP
If you want to return state value instead of Observable see useObservableState.
Gotcha
It is not safe to access other variables from closure directly in the init function. See Gotchas.
TIP
In TypeScript if you want to invoke the callback with no argument, use void instead of undefined type.
Type parameters:
TOutputOutput value within Observable.TInputSelected values.TParamsA tuple of event callback parameters.
Parameters:
| Name | Type | Description |
|---|---|---|
init | (inputs$: Observable<TInputs>): Observable<TOutput> | A pure function that, when applied to an Observable, returns an Observable. |
selector | (args: TParams): TInput | An optional function that transforms an array of event arguments into a single value. |
Returns:
[(...args: TParams): void, Observable<TOutput>] A tuple with the same callback-Observable pair.
Examples:
import { useObservableCallback, useSubscription } from 'observable-hooks'
const Comp = () => {
const [onChange, textChange$] = useObservableCallback<
string,
React.FormEvent<HTMLInputElement>
>(event$ => event$.pipe(
pluck('currentTarget', 'value')
)) // or just use "pluckCurrentTargetValue" helper
useSubscription(textChange$, console.log)
return <input type="text" onChange={onChange} />
}
Transform event arguments:
import { useObservableCallback, identity } from 'observable-hooks'
const [onResize, height$] = useObservableCallback<
number,
number,
[number, number]
>(identity, args => args[1])
// onResize is called with width and height
// height$ gets height values
onResize(100, 500)
# useObservableRef
useObservableRef<TValue>(
initialValue: TValue
): [MutableRefObject<TValue>, BehaviorSubject<TValue>]
useObservableRef<TValue>(
initialValue: TValue | null
): [RefObject<TValue>, BehaviorSubject<TValue>]
useObservableRef<TValue = undefined>(
initialValue?: TValue
): [MutableRefObject<TValue | undefined>, BehaviorSubject<TValue | undefined>]
Returns a mutable ref object and a BehaviorSubject.
Whenever ref.current is changed, the BehaviorSubject will emit the new value.
v4.2.2 Added since v4.2.2.Type parameters:
TValueRef value type.
Parameters:
| Name | Type | Description |
|---|---|---|
initialValue | TValue | An optional initial value. |
Returns:
[MutableRefObject<TValue>, BehaviorSubject<TValue>] A tuple of a ref and a BehaviorSubject.
Examples:
const Comp = () => {
const [elRef, el$] = useObservableRef(null)
useSubscription(el$, console.log)
return <div ref={elRef}>Content</div>
}
const Comp = () => {
const [ref, value$] = useObservableRef(0)
useSubscription(value$, console.log)
return <button onClick={() => ref.current++}>Click</button>
}
# useSubscription
useSubscription<TInput>(
input$: Observable<TInput>,
observer?: PartialObserver<TInput>
): React.MutableRefObject<Subscription | undefined>
useSubscription<TInput>(
input$: Observable<TInput>,
next?: function | null | undefined,
error?: function | null | undefined,
complete?: function | null | undefined
): React.MutableRefObject<Subscription | undefined>
Concurrent mode safe Observable subscription. Accepts an Observable and optional next, error, complete functions. These functions must be in correct order. Use undefined or null for placeholder.
Why use it instead of just useEffect?
useEffect(
() => {
const subscription = input$.subscribe({
next: ...,
error: ...,
complete: ...,
})
return () => {
subscription.unsubscribe()
}
},
[input$]
)
Because:
- The observer callbacks could suffer from stale closure problem. It is not easy to directly reference any local values in the callbacks.
- It is not concurrent mode safe. Tearing could occur during changing of Observables.
v2.0.0 From v2.0.0. useSubscription will ensure the latest callback is called. You can safely reference any closure variable directly inside the callbacks.
v2.3.4 From v2.3.4. When the Observable changes useSubscription will automatically unsubscribe the old one and resubscribe to the new one.
v3.0.0 From v3.0.0. useSubscription is concurrent mode safe. It will prevent observer callbacks being called from stale Observable.
v3.2.0 From v3.2.0. useSubscription accepts an observer object. Huge thanks to OliverJAsh!
To make it concurrent mode compatible, the subscription happens after the render is committed to the screen. Even if the Observable emits synchronous values they still will arrive after the first rendering. Check out useObservableEagerState for skipping initial re-rendering with hot or pure observable that emits synchronous values.
TIP
Note that changes of the observer callbacks will not trigger an emission. If you need that just create another Observable of the callback with useObservable.
Error Handling
Due to the design of RxJS, once an error occurs in an observable, the observable is killed. You can:
- Prevent errors from reaching observables or
catchError(opens new window) in sub-observables. - You can also make the observable as state and replace it on error. It will automatically switch to the new one.
useRenderThrowFromv4.2.0, Observable error can be caught by React error boundary in which you can perform actions to replace the dead Observable.
Type parameters:
TInputInput value within Observable.
Parameters:
| Name | Type | Description |
|---|---|---|
input$ | Observable<TInput> | Input Observable. |
next | (value: TInput): void | null | undefined | Notify when a new value is emitted. |
error | (error: any): void | null | undefined | Notify when a new error is thrown. |
complete | (): void | null | undefined | Notify when the Observable is complete. |
Returns:
React.MutableRefObject<Subscription | undefined> A ref object with the RxJS Subscription. the ref current is undefined on first rendering.
Type parameters:
TInputInput value within Observable.
Parameters:
| Name | Type | Description |
|---|---|---|
input$ | Observable<TInput> | null | undefined | Input Observable. |
observer | PartialObserver<TInput> | Observer |
Returns:
React.MutableRefObject<Subscription | undefined> A ref object with the RxJS Subscription. the ref current is undefined on first rendering.
Examples:
const subscription = useSubscription(events$, e => console.log(e.type))
On complete
const subscription = useSubscription(events$, null, null, () => console.log('complete'
Access closure:
const [debug, setDebug] = useState(false)
const subscription = useSubscription(events$, null, error => {
if (debug) {
console.log(error)
}
})
Invoke props callback
const subscription = useSubscription(events$, props.onEvent)
# useLayoutSubscription
Same as useSubscription except the subscription is established under useLayoutEffect.
Useful when values are needed before DOM paint.
Use it scarcely as it runs synchronously before browser paint. Too many synchronous emissions from the observable could stretch the commit phase.
# useObservableState
useObservableState<TState>(
input$: Observable<TState>
): TState | undefined
useObservableState<TState>(
input$: Observable<TState>,
initialState: TState | (() => TState)
): TState
useObservableState<TState, TInput = TState>(
init: (input$: Observable<TInput>) => Observable<TState>
): [TState | undefined, (input: TInput) => void]
useObservableState<TState, TInput = TState>(
init: (
input$: Observable<TInput>,
initialState: TState
) => Observable<TState>,
initialState: TState | (() => TState)
): [TState, (input: TInput) => void]
A concurrent mode safe sugar to get values from Observables. Read Core Concepts to learn more about its design.
Is can be used in two ways:
- Offer an Observable and an optional initial state.
const output = useObservableState(input$, initialState) - Offer an epic-like function and an optional initial state.
const [output, onInput] = useObservableState( (input$, initialState) => input$.pipe(...), initialState )
These two ways use different hooks, choose either one each time and do not change to the other one during Component's life cycle.
The optional initialState is internally passed to useState(initialState). This means it can be either a state value or a function that returns the state which is for expensive initialization.
The initialState(or its returned result) is also passed to the init function. This is useful if you want to implement reducer pattern which requires an initial state.
To make it concurrent mode compatible, the subscription happens after the render is committed to the screen. Even if the Observable emits synchronous values they still will arrive after the first rendering.
Gotcha
It is not safe to access other variables from closure directly in the init function. See Gotchas.
Error Handling
Due to the design of RxJS, once an error occurs in an observable, the observable is killed. You can:
- Prevent errors from reaching observables or
catchError(opens new window) in sub-observables. - You can also make the observable as state and replace it on error. It will automatically switch to the new one.
useRenderThrowFromv4.2.0, Observable error can be caught by React error boundary in which you can perform actions to replace the dead Observable.
TIP
Want to skip initial re-rendering if the observable emits synchronous values? Check out useObservableEagerState.
TIP
In TypeScript if you want to invoke the callback with no argument, use void instead of undefined type.
Type parameters:
TStateOutput state.
Parameters:
| Name | Type | Description |
|---|---|---|
input$ | Observable<TState> | An Observable. |
initialState | TState | (): TState | Optional initial state. Can be the state value or a function that returns the state. |
Returns:
TState State value.
Type parameters:
TStateOutput state.TInputInput values.
Parameters:
| Name | Type | Description |
|---|---|---|
init | (input$: Observable<TInput>, initialState: TState): Observable<TState> | An epic-like function that, when applied to an Observable and the initial state value, returns an Observable. |
initialState | TState | Optional initial state. Can be the state value or a function that returns the state. |
Returns:
[TState, (input: TInput): void] A tuple with the state and input callback.
Examples:
Consume an Observable:
const count$ = useObservable(() => interval(1000))
const count = useObservableState(count$, 0)
Or with an init function:
const [text, updateText] = useObservableState<string>(
text$ => text$.pipe(delay(1000)),
''
)
Input and output state can be different types
// input: string, output: boolean
const [isValid, updateText] = useObservableState<boolean, string>(text$ =>
text$.pipe(map(text => text.length > 1)),
false
)
Event listener pattern:
import { pluckCurrentTargetValue, useObservableState } from 'observable-hooks'
function App(props) {
const [text, onChange] = useObservableState(pluckCurrentTargetValue, '')
return (
<input onChange={onChange} value={text} />
)
}
Reducer pattern:
const [state, dispatch] = useObservableState(
(action$, initialState) => action$.pipe(
scan((state, action) => {
switch (action.type) {
case 'INCREMENT':
return {
...state,
count: state.count +
(isNaN(action.payload) ? 1 : action.payload)
}
case 'DECREMENT':
return {
...state,
count: state.count -
(isNaN(action.payload) ? 1 : action.payload)
}
default:
return state
}
}, initialState)
),
() => ({ count: 0 })
)
dispatch({ type: 'INCREMENT' })
dispatch({ type: 'DECREMENT', payload: 2 })
# useObservableEagerState
useObservableEagerState<TState>(
state$: Observable<TState>
): TState
Optimized for safely getting synchronous values from hot or pure observables (e.g. BehaviorSubject) without triggering extra initial re-rendering.
This hook will subscribe to the observable at least twice. The first time is for getting synchronous value to prevent extra initial re-rendering. In concurrent mode this may happen more than one time.
WARNING
If the observable is cold and with side effects they will be performed at least twice! It is only safe if the observable is hot or pure.
Type parameters:
TStateOutput state.
Parameters:
| Name | Type | Description |
|---|---|---|
state$ | Observable<TState> | An Observable. |
Returns:
TState state value.
Examples:
const text1$ = new BehaviorSubject('A')
// 'A'
const text1 = useObservableEagerState(text1$)
const text2$ = of('A', 'B', 'C')
// 'C'
const text2 = useObservableEagerState(text2$)
# useLayoutObservableState
v4.1.0 Added since v4.1.0.
Same as useObservableState except the subscription is established under useLayoutEffect.
Unlike useObservableEagerState which gets state value synchronously before the first React rendering, useLayoutObservableState gets state value synchronously after React rendering,while useObservableState gets state value asynchronously after React rendering and browser paint.
Useful when values are needed before DOM paint.
Use it scarcely as it runs synchronously before browser paint. Too many synchronous emissions from the observable could stretch the commit phase.
# useObservableGetState
useObservableGetState<TState>(
state$: Observable<TState>,
initialState: TState | (() => TState)
): TState
useObservableGetState<TState, A extends keyof TState>(
state$: Observable<TState>,
initialState: TState[A] | (() => TState[A]),
pA: A
): TState[A]
useObservableGetState<
TState,
A extends keyof TState,
B extends keyof TState[A]
>(
state$: Observable<TState>,
initialState: TState[A][B] | (() => TState[A][B]),
pA: A,
pB: B
): TState[A][B]
...
Inspired by lodash get.
v2.3.0 Added since v2.3.0. Get value at path of state from an Observable.
v3.0.0 From v3.0.0. An initial state must be provided.
Only changes of the resulted value will trigger a rerendering.
WARNING
Unreachable path will throw errors.
Type parameters:
TStateOutput state.
Parameters:
| Name | Type | Description |
|---|---|---|
state$ | Observable<TState> | An Observable. |
initialState | undefined | null | TState[...] | (() => TState[...]) | Initial value. Can be the value or a function that returns the value. |
pA | keyof TState | Key of TState. |
pB | keyof TState[A] | Key of TState[A]. |
pC | keyof TState[A][B] | Key of TState[A][B]. |
... | ... | .... |
Returns:
TState[...] Initial value or the value at path of TState.
Examples:
const state$ = of({ a: { b: { c: 'value' } } })
// 'default' on first rendering
// 'value' on next rendering
const text = useObservableGetState(state$, 'default', 'a', 'b', 'c')
# useObservablePickState
useObservablePickState<
TState,
TKeys extends keyof TState,
TInitial extends null | undefined | void
>(
state$: Observable<TState>,
initialState: TInitial,
...keys: TKeys[]
): { [K in TKeys]: TState[K] } | TInitial
useObservablePickState<TState, TKeys extends keyof TState>(
state$: Observable<TState>,
initialState:
| { [K in TKeys]: TState[K] }
| (() => { [K in TKeys]: TState[K] }),
...keys: TKeys[]
): { [K in TKeys]: TState[K] }
Inspired by lodash pick.
v2.3.2 Added since v2.3.2. Creates an object composed of the picked state properties.
v3.0.0 From v3.0.0. An initial state must be provided.
Changes of any of these properties will trigger a rerendering.
WARNING
Unreachable path will throw errors.
Type parameters:
TStateOutput state.TKeyskeys of state.
Parameters:
| Name | Type | Description |
|---|---|---|
state$ | Observable<TState> | An Observable. |
initialState | undefined | null | { [K in TKeys]: TState[K] } | (() => { [K in TKeys]: TState[K] }) | Initial value. Can be the value or a function that returns the value. |
...path | Array<keyof TState> | Keys of TState. |
Returns:
{ [K in TKeys]: TState[K] } Initial value or partial TState.
Examples:
const state$ = of({ a: 'a', b: 'b', c: 'c', d: 'd' })
// { a: '', b: '', c: '' } on first rendering
// { a: 'a', b: 'b', c: 'c' } on next rendering
const picked = useObservablePickState(
state$,
() =>({ a: '', b: '', c: '' }),
'a', 'b', 'c'
)
# useRenderThrow
useRenderThrow<TInput>(input$: Observable<TInput>): Observable<TInput>
Captures Observable errors and re-throw them as React render errors so that they can be caught by ErrorBoundary.
v4.2.0 Added since v4.2.0.
Type parameters:
TInputInput Observable
Parameters:
| Name | Type | Description |
|---|---|---|
input$ | Observable<TInput> | An Observable. |
Returns:
Observable<TInput> An Observable.
Examples:
const state$ = useObservable(() => of(({ a: 'a', b: 'b', c: 'c', d: 'd' })))
const enhanced$ = useRenderThrow(state$)
Suspense →