# 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:
TOutput
Output value within Observable.TInputs
A 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:
TOutput
Output value within Observable.TInput
Selected values.TParams
A 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:
TValue
Ref 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.
useRenderThrow
Fromv4.2.0
, Observable error can be caught by React error boundary in which you can perform actions to replace the dead Observable.
Type parameters:
TInput
Input 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:
TInput
Input 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.
useRenderThrow
Fromv4.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:
TState
Output 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:
TState
Output state.TInput
Input 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:
TState
Output 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:
TState
Output 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:
TState
Output state.TKeys
keys 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:
TInput
Input 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 →