# Core

# useObservable

useObservable<TOutput>(init: function): Observable<TOutput>
useObservable<TOutput, TInputs>(
  init: function,
  inputs: TInputs
): Observable<TOutput>

React function components will be called many times during its life cycle. Create or transform Observables in init function so that the operations won't be repeatedly performed.

Accepts a function that returns an Observable. Optionally accepts an array of dependencies which will be turned into Observable and be passed to the init function.

WARNING

useObservable will call init once and always return the same Observable. It is not safe to access closure (except other Observables) directly inside init. You should use ref or pass them as dependencies through the second argument.

CAUTION

Due to rules of hooks you can either offer or omit the dependencies array but do not change to one another during Component's life cycle. The length of the dependencies array must also be fixed.

Type parameters:

  • TOutput Output value within Observable.
  • TInputs An 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)))

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 to listen props and state changes.

This is useful if you need values before next paint.

# 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 value output instead of Observable see example on useObservableState.

WARNING

useObservableCallback will call init once and always return the same Observable. It is not safe to access closure (except other Observables) directly inside init. Use ref or useObservable with withLatestFrom instead.

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 hegiht
// height$ gets height values
onResize(100, 500)

# useSubscription

useSubscription<TInput>(
  input$: Observable<TInput>,
  next?: function | null | undefined,
  error?: function | null | undefined,
  complete?: function | null | undefined
): Subscription

Subscription will auto-unsubscribe when unmount, you can also unsubscribe manually.

v2.0.0 From v2.0.0 you can reference closure variables directly inside callback. useSubscription will ensure the latest callback is called. v2.3.4 From v2.3.4 when the Observable changes `useSubscription` will automatically unsubscribe the old one and resubscribe to the new one.

Accepts an Observable and optional next, error, complete functions. These functions must be in correct order. Use undefined or null for placeholder.

WARNING

Note that changes of callbacks will not trigger an emission. If you need that just create another Observable of the callback with useObservable.

WARNING

Due to the design of RxJS, once an error occurs in an observable, the observable is killed. You should prevent errors from reaching observables or catchError in sub-observables. You can also make the observable as state and replace it on error. useSubscription will automatically switch to the new one.

Type parameters:

  • TInput Input value within Observable.

Parameters:

Name Type Description
input$ Observable<TInput> | null | undefined 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:

Subscription RxJS Subscription object.

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)

# useObservableState

useObservableState<TState, TSyncInit>(
  input$: Observable<TState>
): TSyncInit extends false ? TState | undefined : TState
useObservableState<TState>(
  input$: Observable<TState>,
  initState: TState
): TState
useObservableState<TState, TInput, TSyncInit>(
  init: function
): [TSyncInit extends false ? TState | undefined : TState, function]
useObservableState<TState, TInput>(
  init: function,
  initState: TState
): [TState, function]

A helper to get value from an Observable.

Accept an optional initState which will be directly passed to the result. But if sync values are also emitted from the Observable initState will be ignored.

You can also use the regular useState with useSubscription directly which will trigger extra initial re-renders when sync values are emitted from the Observable (e.g. of or startWith).

TIP

It it recommended to use initState for simple primitive value. For others, init with the Observable to save some (re)computations.

CAUTION

Due to rules of hooks you can offer either a function or an Observable as the first argument but do not change to one another during Component's life cycle.

WARNING

useObservableState will call init once and always return the same Observable. It is not safe to access closure (except other Observables) directly inside init. Use ref or useObservable with withLatestFrom instead.

WARNING

Unhandled errors from the observable will be caught and logged. In non-production mode the context of useObservableState itself will also be logged. But do note that due to the design of RxJS, once an error occurs in an observable, the observable is killed. You should prevent errors from reaching observables or catchError in sub-observables.

v2.1.2 From v2.1.2 you can pass true to TSyncInit generic to remove undefined from resulted type.

Type parameters:

  • TState Output state.
  • TSyncInit Does the Observable emit sync values?

Parameters:

Name Type Description
input$ Observable<TState> An Observable.
initState TState Optional initial state.

Returns:

TState State value.


Type parameters:

  • TState Output state.
  • TInput Input values.
  • TSyncInit Does the Observable emit sync values?

Parameters:

Name Type Description
init (input$: Observable<TInput>): Observable<TState> A pure function that, when applied to an Observable, returns an Observable.
initState TState Optional initial state.

Returns:

[TState, (input: TInput) => void] A tuple with state-setState pair.


Examples:

Offer an Observable

const count$ = useObservable(() => interval(1000))
const count = useObservableState(count$)

Offer an init function

const [text, updateText] = useObservableState<string>(
  text$ => text$.pipe(delay(1000))
)

With different types

const [isValid, updateText] = useObservableState<boolean, string>(text$ =>
  text$.pipe(map(text => text.length > 1))
)

With init value:

const count$ = useObservable(() => interval(1000))
const count = useObservableState(count$, -1)

Or function with init value:

// Types now can be inferred
const [text, updateText] = useObservableState(
  text$ => text$.pipe(delay(1000)),
  'init text'
)

Or use startWith. Pass true to TSyncInit generic to remove undefined from resulted type.

// time is `string` now instead of `string | undefined`
const [text, updateText] = useObservableState<string, string, true>(
  text$ => text$.pipe(delay(1000), startWith('init text'))
)

For Observables you can also use the non-null assertion operator !.

// time is `string` now instead of `string | undefined`
const time = useObservableState(
  useObservable(() =>
    interval(1000).pipe(
      startWith(-1),
      map(() => new Date().toLocaleString())
    )
  )
)!

Event listener:

import { pluckCurrentTargetValue, useObservableState } from 'observable-hooks'

const [text, onChange] = useObservableState<
  string,
  React.ChangeEvent<HTMLInputElement>
>(pluckCurrentTargetValue, '')

# useObservableGetState

useObservableGetState<TState>(
  state$: Observable<TState>
): TState | undefined
useObservableGetState<
  TState,
  A extends keyof TState,
>(
  state$: Observable<TState>,
  pA: A,
): TState[A] | undefined
useObservableGetState<
  TState,
  A extends keyof TState,
  B extends keyof TState[A],
>(
  state$: Observable<TState>,
  pA: A,
  pB: B,
): TState[A][B] | undefined
...
v2.3.0 From v2.3.0. Get value at path of state from an Observable.

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.
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[A][B]... | undefined Value at path of TState.

Examples:

const state$ = of({ a: { b: { c: 'abc' } } })

// 'abc'
const text = useObservableGetState(state$, 'a', 'b', 'c')!

# useObservablePickState

useObservablePickState<
  TState,
  TKeys extends Array<keyof TState>
>(
  state$: Observable<TState>,
  ...keys: TKeys
): { [K in TKeys[number]]: TState[K] } | undefined
v2.3.2 From v2.3.2. Creates an object composed of the picked state properties.

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.
...path Array<keyof TState> Keys of TState.

Returns:

{ [K in TKeys[number]]: TState[K] } | undefined Partial TState.

Examples:

const state$ = of({ a: 'a', b: 'b', c: 'c', d: 'd' })

// { a: 'a', b: 'b', c: 'c' }
const picked = useObservablePickState(state$, 'a', 'b', 'c')!