### Install observable-hooks Source: https://github.com/crimx/observable-hooks/blob/main/README.md Install the library using pnpm. ```bash pnpm add observable-hooks ``` -------------------------------- ### Install Dependencies for Development Source: https://github.com/crimx/observable-hooks/blob/main/README.md Install project dependencies using pnpm. ```bash pnpm i ``` -------------------------------- ### Install observable-hooks with RxJS and React Source: https://github.com/crimx/observable-hooks/blob/main/docs/guide/README.md Install the library along with its peer dependencies, RxJS and React, using either yarn or npm. ```bash yarn add observable-hooks rxjs react ``` ```bash npm install --save observable-hooks rxjs react ``` -------------------------------- ### ObservableResource Setup Source: https://github.com/crimx/observable-hooks/blob/main/docs/guide/render-as-you-fetch-suspense.md Sets up an ObservableResource to manage data fetching for posts. It uses a Subject to trigger fetches and switchMap to handle asynchronous operations. ```javascript // api.js import { ObservableResource } from 'observable-hooks' const postResource$$ = new Subject() export const postsResource = new ObservableResource(postResource$$.pipe( switchMap(id => fakePostsXHR(id)) )) export function fetchPosts(id) { postResource$$.next(id) } ``` -------------------------------- ### useEffect Subscription Example Source: https://github.com/crimx/observable-hooks/blob/main/docs/api/README.md Illustrates a basic subscription using useEffect, highlighting potential issues like stale closures and lack of concurrent mode safety that useSubscription addresses. ```js useEffect( () => { const subscription = input$.subscribe({ next: ..., error: ..., complete: ..., }) return () => { subscription.unsubscribe() } }, [input$] ) ``` -------------------------------- ### useSubscription with Props Callback Source: https://github.com/crimx/observable-hooks/blob/main/docs/api/README.md An example of passing a prop callback function to useSubscription to handle emitted events. ```typescript const subscription = useSubscription(events$, props.onEvent) ``` -------------------------------- ### Basic useSubscription Usage Source: https://github.com/crimx/observable-hooks/blob/main/docs/api/README.md A simple example of using useSubscription to subscribe to an observable and log emitted values. ```typescript const subscription = useSubscription(events$, e => console.log(e.type)) ``` -------------------------------- ### useObservableState with Different Input and Output Types Source: https://github.com/crimx/observable-hooks/blob/main/docs/api/README.md This example demonstrates using useObservableState when the input and output types of the observable are different. It allows for transformations like mapping string input to boolean output. ```typescript // input: string, output: boolean const [isValid, updateText] = useObservableState(text$ => text$.pipe(map(text => text.length > 1)), false ) ``` -------------------------------- ### Picking State Properties with useObservablePickState Source: https://github.com/crimx/observable-hooks/blob/main/docs/api/README.md Example demonstrating how to use useObservablePickState to pick specific properties ('a', 'b', 'c') from an observable state. The initial state is provided as a function that returns an object with empty strings for the picked properties. ```typescript 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' ) ``` -------------------------------- ### useObservableGetState Source: https://github.com/crimx/observable-hooks/blob/main/docs/api/README.md Retrieves a value at a specified path from an Observable state, inspired by lodash's get. An initial state must be provided, and only changes to the resulting value trigger re-renders. Unreachable paths will throw errors. ```APIDOC ## useObservableGetState ```typescript useObservableGetState(state$: Observable, initialState: TState | (() => TState)): TState useObservableGetState(state$: Observable, initialState: TState[A] | (() => TState[A]), pA: A): TState[A] useObservableGetState(state$: Observable, initialState: TState[A][B] | (() => TState[A][B]), pA: A, pB: B): TState[A][B] ... ``` Inspired by lodash `get`.

Added since v2.3.0. Get value at path of state from an Observable.

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` | 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:** ```typescript const state$ = of({ a: { b: { c: 'value' } } }) // 'default' on first rendering // 'value' on next rendering const text = useObservableGetState(state$, 'default', 'a', 'b', 'c') ``` ``` -------------------------------- ### useLayoutObservableState Source: https://github.com/crimx/observable-hooks/blob/main/docs/api/README.md Similar to useObservableState but establishes the subscription within useLayoutEffect. It gets the state value synchronously after React rendering, useful for values needed before DOM paint. Use sparingly to avoid stretching the commit phase. ```APIDOC ## useLayoutObservableState Same as [`useObservableState`](#useobservablestate) except the subscription is established under `useLayoutEffect`. Unlike [`useObservableEagerState`](#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. ``` -------------------------------- ### Use Observable Callback with Argument Selector Source: https://github.com/crimx/observable-hooks/blob/main/docs/api/README.md Demonstrates using `useObservableCallback` with a selector function to process callback arguments. The `init` function receives an observable of arguments, and the selector transforms these arguments into the desired output for the observable. This example shows how to extract the second argument (height) from a callback that receives two arguments (width, height). ```typescript 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 with Primitive Value Source: https://github.com/crimx/observable-hooks/blob/main/docs/api/README.md Example demonstrating how to use useObservableRef with a primitive value like a number. Clicking the button increments the ref's current value, which then emits through the BehaviorSubject. ```tsx const Comp = () => { const [ref, value$] = useObservableRef(0) useSubscription(value$, console.log) return } ``` -------------------------------- ### Using useRenderThrow with useObservable Source: https://github.com/crimx/observable-hooks/blob/main/docs/api/README.md Example demonstrating how to use the useRenderThrow hook with an Observable created by useObservable. This enhances the observable to re-throw errors as React render errors. ```typescript const state$ = useObservable(() => of(({ a: 'a', b: 'b', c: 'c', d: 'd' }))) const enhanced$ = useRenderThrow(state$) ``` -------------------------------- ### useObservableState with Observable and Initial State Source: https://github.com/crimx/observable-hooks/blob/main/docs/api/README.md Use this hook to get values from an Observable with an optional initial state. The subscription happens after the component commits to the screen, ensuring concurrent mode compatibility. ```typescript useObservableState( input$: Observable ): TState | undefined useObservableState( input$: Observable, initialState: TState | (() => TState) ): TState ``` ```javascript const output = useObservableState(input$, initialState) ``` ```typescript const count$ = useObservable(() => interval(1000)) const count = useObservableState(count$, 0) ``` -------------------------------- ### useObservableEagerState Source: https://github.com/crimx/observable-hooks/blob/main/docs/api/README.md 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. ```APIDOC ## useObservableEagerState ### Description 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 #### Path Parameters - `state$` (`Observable`) - Required - An Observable. ### Returns - `TState` - state value. ### Request Example ```typescript const text1$ = new BehaviorSubject('A') // 'A' const text1 = useObservableEagerState(text1$) const text2$ = of('A', 'B', 'C') // 'C' const text2 = useObservableEagerState(text2$) ``` ``` -------------------------------- ### useObservableRef with DOM Element Source: https://github.com/crimx/observable-hooks/blob/main/docs/api/README.md Example of using useObservableRef to link a DOM element's ref to a BehaviorSubject. Changes to the element's ref will trigger emissions from the subject. ```tsx const Comp = () => { const [elRef, el$] = useObservableRef(null) useSubscription(el$, console.log) return
Content
} ``` -------------------------------- ### useObservableState Hook Source: https://github.com/crimx/observable-hooks/blob/main/docs/api/README.md Provides a concurrent mode safe sugar to get values from Observables. It can be used in two ways: offering an Observable with an optional initial state, or offering an epic-like function with an optional initial state. The optional initialState is internally passed to useState, accepting either a state value or a function for expensive initialization. The initialState is also passed to the init function, useful for reducer patterns. Subscription happens after the render is committed to the screen for concurrent mode compatibility. ```APIDOC ## useObservableState Provides a concurrent mode safe sugar to get values from Observables. ### Usage 1. **Offer an Observable and an optional initial state:** ```js const output = useObservableState(input$, initialState) ``` 2. **Offer an epic-like function and an optional initial state:** ```js const [output, onInput] = useObservableState(init, initialState) ``` ### Parameters **Overload 1:** - **`input$`** (`Observable`) - An Observable. - **`initialState`** (`TState | (() => TState)`) - Optional initial state. Can be the state value or a function that returns the state. **Returns:** - `TState` - State value. **Overload 2:** - **`init`** (`(input$: Observable, initialState: TState) => Observable`) - An epic-like function that, when applied to an Observable and the initial state value, returns an Observable. - **`initialState`** (`TState | (() => 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. ### Type Parameters - **`TState`**: Output state. - **`TInput`**: Input values (for overload 2). ### Examples **Consume an Observable:** ```typescript import { interval } from 'rxjs'; import { useObservable } from 'observable-hooks'; const count$ = useObservable(() => interval(1000)); const count = useObservableState(count$, 0); ``` **With an `init` function:** ```typescript import { delay } from 'rxjs/operators'; const [text, updateText] = useObservableState( text$ => text$.pipe(delay(1000)), '' ); ``` **Different input and output types:** ```typescript import { map } from 'rxjs/operators'; // input: string, output: boolean const [isValid, updateText] = useObservableState( text$ => text$.pipe(map(text => text.length > 1)), false ); ``` **Event listener pattern:** ```javascript import { pluckCurrentTargetValue, useObservableState } from 'observable-hooks'; function App(props) { const [text, onChange] = useObservableState(pluckCurrentTargetValue, ''); return ( ); } ``` ### Gotcha It is not safe to access other variables from closure directly in the `init` function. See [Gotchas](../guide/gotchas.md). ### 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` in sub-observables. - Make the observable as state and replace it on error. It will automatically switch to the new one. - Use `useRenderThrow` (From v4.2.0) to catch Observable errors with React error boundaries, allowing actions to replace the dead Observable. ### Note In TypeScript, to invoke the callback with no argument, use `void` instead of `undefined` type. ``` -------------------------------- ### Using useObservableState with Observable Value from Context Source: https://github.com/crimx/observable-hooks/blob/main/docs/guide/context.md Consume an Observable from React Context and get its latest value using `useObservableState`. This hook internally uses `useSubscription`. ```javascript const num$ = useContext(ObservableValueContext) const num = useObservableState(num$) console.log('useObservableState', num) ``` -------------------------------- ### Get Nested State from Observable Source: https://github.com/crimx/observable-hooks/blob/main/docs/api/README.md Use `useObservableGetState` to retrieve a value at a specific path within an observable's state. An initial state must be provided. Only changes to the resulting value will trigger a re-render. Be aware that unreachable paths will throw errors. ```typescript useObservableGetState( state$: Observable, initialState: TState | (() => TState) ): TState useObservableGetState( state$: Observable, initialState: TState[A] | (() => TState[A]), pA: A ): TState[A] useObservableGetState< TState, A extends keyof TState, B extends keyof TState[A] >( state$: Observable, initialState: TState[A][B] | (() => TState[A][B]), pA: A, pB: B ): TState[A][B] ... ``` ```typescript const state$ = of({ a: { b: { c: 'value' } } }) // 'default' on first rendering // 'value' on next rendering const text = useObservableGetState(state$, 'default', 'a', 'b', 'c') ``` -------------------------------- ### Auto-cancellation of RxJS Operations in TypeScript Source: https://github.com/crimx/observable-hooks/blob/main/docs/examples/README.md Demonstrates how to automatically cancel RxJS operations using `switchMap` with `useObservable` and `useSubscription`. This is useful for preventing race conditions or unnecessary work when an observable's source value changes, such as in this example where a beacon is sent only if a checkbox is checked. ```tsx import React, { FC, useState } from 'react' import { timer, empty } from 'rxjs' import { switchMap, mapTo } from 'rxjs/operators' import { useObservable, useSubscription } from 'observable-hooks' const sendBeacon = (beacon: string) => fetch(`https://api?beacon=${beacon}`) export interface AppProps { beacon: string } export const App: FC = props => { const [shouldSendBeacon, setShouldSendBeacon] = useState(false) const beacon$ = useObservable( inputs$ => inputs$.pipe( // auto-cancelation switchMap(([shouldSendBeacon, beacon]) => shouldSendBeacon ? timer(1000).pipe(mapTo(beacon)) : empty() ) ), [shouldSendBeacon, props.beacon] ) useSubscription(beacon$, sendBeacon) return ( ) } ``` -------------------------------- ### Run Tests Source: https://github.com/crimx/observable-hooks/blob/main/README.md Execute the project's tests. ```bash pnpm test ``` -------------------------------- ### Import from observable-hooks Source: https://github.com/crimx/observable-hooks/blob/main/docs/guide/README.md Import necessary components and hooks from the 'observable-hooks' entry point. ```javascript import { ... } from 'observable-hooks' ``` -------------------------------- ### Initial Resource Fetch Source: https://github.com/crimx/observable-hooks/blob/main/docs/guide/render-as-you-fetch-suspense.md Defines the initial resource for data fetching. This is typically used to set up the initial state before Suspense takes over. ```javascript const initialResource = fetchProfileData(0); function App() { const [resource, setResource] = useState(initialResource); ``` -------------------------------- ### useSubscription with Completion Callback Source: https://github.com/crimx/observable-hooks/blob/main/docs/api/README.md Demonstrates how to provide a completion callback to useSubscription to execute logic when the observable completes. ```typescript const subscription = useSubscription(events$, null, null, () => console.log('complete' ``` -------------------------------- ### Basic Usage with useObservableState Source: https://github.com/crimx/observable-hooks/blob/main/README.md Demonstrates using `useObservableState` to manage component state based on an Observable stream. The `transformTypingStatus` function is pure and can be tested independently. ```jsx import * as React from "react"; import { useObservableState } from "observable-hooks"; import { timer } from "rxjs"; import { switchMap, mapTo, startWith } from "rxjs/operators"; const App = () => { const [isTyping, updateIsTyping] = useObservableState( transformTypingStatus, false ); return (

{isTyping ? "Good you are typing." : "Why stop typing?"}

); }; // Logic is pure and can be tested like Epic in redux-observable function transformTypingStatus(event$) { return event$.pipe( switchMap(() => timer(1000).pipe(mapTo(false), startWith(true))) ); } ``` -------------------------------- ### useForceUpdate Source: https://github.com/crimx/observable-hooks/blob/main/docs/api/helpers.md Force re-renders Component. ```APIDOC ## useForceUpdate ### Description Force re-renders Component. ### Parameters #### Path Parameters - None #### Query Parameters - None #### Request Body - None ### Returns `() => void` A callback which re-renders component when called. ### Request Example ```typescript const forceUpdate = useForceUpdate(); // Call forceUpdate() to trigger a re-render ``` ### Response #### Success Response (200) - None #### Response Example - None ``` -------------------------------- ### useObservableEagerState Hook Source: https://github.com/crimx/observable-hooks/blob/main/docs/api/README.md Safely gets synchronous values from hot or pure observables without extra initial re-renders. Be cautious with cold observables as side effects may be performed multiple times. ```typescript const text1$ = new BehaviorSubject('A') // 'A' const text1 = useObservableEagerState(text1$) const text2$ = of('A', 'B', 'C') // 'C' const text2 = useObservableEagerState(text2$) ``` -------------------------------- ### Observable to Callbacks with useSubscription Source: https://github.com/crimx/observable-hooks/blob/main/docs/guide/core-concepts.md Demonstrates using `useSubscription` to trigger callbacks in the Normal World when new values arrive from an observable stream in the Observable World. This is preferred over manual `useEffect` for handling observable side effects. ```text +--------------------------------+ | Observable World | +--------------------------------+ | | | input$ | | | +-------------------+------------+ | | | v useSubscription(input$, onNext) | | | | +---------------------------v----+ | Normal World | +--------------------------------+ | | | const onNext = v => log(v) | | | +--------------------------------+ ``` -------------------------------- ### identity Source: https://github.com/crimx/observable-hooks/blob/main/docs/api/helpers.md Returns the first argument it receives. ```APIDOC ## identity ### Description Returns the first argument it receives. ### Parameters #### Path Parameters - None #### Query Parameters - None #### Request Body - None ### Parameters - **value** (T) - Required - Any value. ### Returns `T` The first argument. ### Request Example ```typescript // Example usage for identity function const result = identity(5); ``` ### Response #### Success Response (200) - None #### Response Example - None ``` -------------------------------- ### Create Observable with useObservable Source: https://github.com/crimx/observable-hooks/blob/main/docs/api/README.md Demonstrates creating a new Observable using `useObservable` that emits the current date every second. ```typescript const now$ = useObservable( () => interval(1000).pipe( map(() => new Date().toLocaleString()) ) ) ``` -------------------------------- ### useObservableState with Init Function and Initial State Source: https://github.com/crimx/observable-hooks/blob/main/docs/api/README.md Use this hook when you need to transform an input Observable into an output Observable, providing an optional initial state. It returns the current state and a function to update the input. ```typescript useObservableState( init: (input$: Observable, initialState: TState ) => Observable, initialState: TState | (() => TState) ): [TState, (input: TInput) => void] ``` ```javascript const [output, onInput] = useObservableState( (input$, initialState) => input$.pipe(...), initialState ) ``` ```typescript const [text, updateText] = useObservableState( text$ => text$.pipe(delay(1000)), '' ) ``` -------------------------------- ### Direct Dependencies Pattern Source: https://github.com/crimx/observable-hooks/blob/main/docs/guide/react-independent-epics.md Passing local observables directly as dependencies to `useObservable` to manage internal state. ```javascript const [onChange, textChange$] = useObservableCallback(event$ => event$.pipe(...)) const enhanced$ = useObservable( inputs$ => { const textChange$ = inputs$.pipe( distinctUntilKeyChanged(2) switchMap(inputs => inputs[2]) ) return inputs$.pipe( withLatestFrom(textChange$) ... ) }, [props.a, stateB, textChange$] ) ``` -------------------------------- ### Basic useObservable Hook Source: https://github.com/crimx/observable-hooks/blob/main/docs/guide/react-independent-epics.md Standard usage of the `useObservable` hook with a simple epic function and dependencies. ```javascript const enhanced$ = useObservable( inputs$ => inputs$.pipe( ... ), [props.a, stateB] ) ``` -------------------------------- ### Lint Code Source: https://github.com/crimx/observable-hooks/blob/main/README.md Run the code linter to check for style and potential errors. ```bash pnpm lint ``` -------------------------------- ### Using Reusable Epic with useObservable Source: https://github.com/crimx/observable-hooks/blob/main/docs/guide/react-independent-epics.md Importing and using a pre-defined, reusable epic function with the `useObservable` hook. ```javascript import { transformText } from 'path/to/logic/text' const enhanced$ = useObservable( transformText, [props.a, stateB] ) ``` -------------------------------- ### Observable to State Connection Source: https://github.com/crimx/observable-hooks/blob/main/docs/guide/core-concepts.md Shows how `useObservableState` connects an observable stream (`input$`) to a React state variable (`output`). The observable pipeline resides in the Observable World, and the resulting state is used in the Normal World. ```text +--------------------------------+ | Observable World | +--------------------------------+ | | | input$ | | | +--------------------+-----------+ | | | v-----------+ const output = useObservableState( | input$, | initialOutput | ) | | | | +--------v-----------------------+ | Normal World | +--------------------------------+ | | |

{output}

| | | +--------------------------------+ ``` -------------------------------- ### useObservableCallback with useObservable Source: https://github.com/crimx/observable-hooks/blob/main/docs/guide/react-independent-epics.md Combining `useObservableCallback` for event handling with `useObservable` for derived state, introducing local dependencies. ```javascript const [onChange, textChange$] = useObservableCallback(event$ => event$.pipe(...)) const enhanced$ = useObservable( inputs$ => inputs$.pipe( withLatestFrom(textChange$) ... ), [props.a, stateB] ) ``` -------------------------------- ### Consuming ObservableResource with useObservableSuspense Source: https://github.com/crimx/observable-hooks/blob/main/docs/guide/render-as-you-fetch-suspense.md Demonstrates how to use the `useObservableSuspense` hook to consume an ObservableResource within a React component under a Suspense boundary. It fetches posts and displays them. ```jsx // App.jsx import { useObservableSuspense } from 'observable-hooks' import { postsResource, fetchPosts } from './api' fetchPosts('crimx') function ProfilePage() { return ( Loading posts...}> ) } function ProfileTimeline() { // Try to read posts, although they might not have loaded yet const posts = useObservableSuspense(postsResource) return (
    {posts.map(post => (
  • {post.text}
  • ))}
) } ``` -------------------------------- ### useObservableState for Event Listener Pattern Source: https://github.com/crimx/observable-hooks/blob/main/docs/api/README.md This snippet shows how to use useObservableState to create an event listener pattern, where changes from an input element are directly mapped to state updates. ```javascript import { pluckCurrentTargetValue, useObservableState } from 'observable-hooks' function App(props) { const [text, onChange] = useObservableState(pluckCurrentTargetValue, '') return ( ) } ``` -------------------------------- ### Higher-order Dependencies Pattern Source: https://github.com/crimx/observable-hooks/blob/main/docs/guide/react-independent-epics.md Separating normal dependencies from observable dependencies by using `useObservable` for meta-values and then combining them. ```javascript const [onChange, textChange$] = useObservableCallback(event$ => event$.pipe(...)) const metaValues$ = useObservable(identity, [props.a, stateB]) const enhanced$ = useObservable( inputs$ => inputs$.pipe( switchMap(([metaValues$, textChange$]) => metaValues$.pipe( withLatestFrom(textChange$) ... )) ), [metaValues$, textChange$] ) ``` -------------------------------- ### Create Observable from Hook Dependencies Source: https://github.com/crimx/observable-hooks/blob/main/docs/guide/core-concepts.md Use `useObservable` to create an observable that depends on hook dependencies. The observable will re-emit when any of the dependencies change. ```javascript const output$ = useObservable( transform, [props.A, state, ctx] ) ``` -------------------------------- ### Listen to Props or State Change with useObservable Source: https://github.com/crimx/observable-hooks/blob/main/docs/api/README.md Use `useObservable` to create an Observable that reacts to changes in React props or state. The `inputs$` observable emits an array of dependencies when they change. ```typescript interface CompProps { isOpen: boolean } const Comp: React.FC = 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] ) } ``` -------------------------------- ### ObservableResource Constructor Source: https://github.com/crimx/observable-hooks/blob/main/docs/api/suspense.md Initializes an ObservableResource to bridge RxJS Observables with React Suspense. Use the `isSuccess` function to determine the success state of emitted values, otherwise a Suspense is triggered. ```typescript new ObservableResource( input$: Observable, isSuccess?: (value: TInput) => value is TOutput ): ObservableResource ``` -------------------------------- ### useObservableCallback Source: https://github.com/crimx/observable-hooks/blob/main/docs/api/README.md Returns a callback function and an events Observable. The Observable emits the first argument of the callback when it's called. Optionally accepts a selector function to transform event arguments. ```APIDOC ## useObservableCallback Returns a callback function and an events Observable. Whenever the callback is called, the Observable will emit the first argument of the callback. From v2.1.0 optionally accepts a selector function which transforms an array of event arguments into a single value. ### Method Signature ```typescript useObservableCallback(init?: function, selector?: undefined | function): [function, Observable] ``` ### Type Parameters - `TOutput` Output value within Observable. - `TInput` Selected values. - `TParams` A tuple of event callback parameters. ### Parameters #### `init` - **Type**: `(inputs$: Observable): Observable` - **Description**: A pure function that, when applied to an Observable, returns an Observable. #### `selector` - **Type**: `(args: TParams): TInput` - **Description**: An optional function that transforms an array of event arguments into a single value. ### Returns - **Type**: `[(...args: TParams): void, Observable]` - **Description**: A tuple with the same callback-Observable pair. ### Examples #### Basic Usage ```typescript import { useObservableCallback, useSubscription } from 'observable-hooks' const Comp = () => { const [onChange, textChange$] = useObservableCallback< string, React.FormEvent >( event$ => event$.pipe( pluck('currentTarget', 'value') ) // or just use "pluckCurrentTargetValue" helper ) useSubscription(textChange$, console.log) return } ``` #### Transform event arguments ```typescript 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) ``` ``` -------------------------------- ### Create Observable from Function Callbacks Source: https://github.com/crimx/observable-hooks/blob/main/docs/guide/core-concepts.md Use `useObservableCallback` to create an observable from a function callback. This is useful for scenarios where you want to trigger an observable stream from a user interaction or event. ```javascript const [onInput, output$] = useObservableCallback( transform ) ``` -------------------------------- ### useSubscription Signature Source: https://github.com/crimx/observable-hooks/blob/main/docs/api/README.md Defines the two possible signatures for useSubscription: one accepting individual observer functions and another accepting a PartialObserver object. The hook returns a ref object containing the RxJS Subscription. ```typescript useSubscription( input$: Observable, observer?: PartialObserver ): React.MutableRefObject useSubscription( input$: Observable, next?: function | null | undefined, error?: function | null | undefined, complete?: function | null | undefined ): React.MutableRefObject ``` -------------------------------- ### Combine Multiple Observables with useObservable Source: https://github.com/crimx/observable-hooks/blob/main/docs/guide/core-concepts.md Use `useObservable` to combine multiple observables like `fromProps$`, `fromState$`, and `fromGlobal$` into a single output observable. This is useful for complex observable flow designs. ```javascript const output$ = useObservable( () => combineLatest([ fromProps$, fromState$, fromGlobal$ ]) ) ``` -------------------------------- ### Conditional Rendering with RxJS Streams Source: https://github.com/crimx/observable-hooks/blob/main/docs/examples/README.md Use `useObservableState` to manage a stream of React elements for conditional rendering. This approach avoids the overhead of Suspense or Error Boundaries by directly emitting UI components, offering a performant alternative for dynamic content updates. ```jsx import { from, of } from 'rxjs' import { map, switchMap, startWith, catchError } from 'rxjs/operators' import { useObservableState } from 'observable-hooks' import { fetchData } from './api' import { DefaultUI, SuccessUI, LoadingUI, FailedUI } from './components' export function App() { const [status, onFetchData] = useObservableState( event$ => event$.pipe( // OMG I don't have to deal with race condition switchMap(event => from(fetchData(event.currentTarget.id)).pipe( map(value => ), // handle errors on sub-stream so that main stream stays alive catchError(error => of()), // show loading state immediately startWith() ) ) ), () => // initial state ) return (
{status}
) } ``` -------------------------------- ### useSubscription with Closure Access Source: https://github.com/crimx/observable-hooks/blob/main/docs/api/README.md Shows how useSubscription safely accesses closure variables, such as the 'debug' state, within its error callback. ```typescript const [debug, setDebug] = useState(false) const subscription = useSubscription(events$, null, error => { if (debug) { console.log(error) } }) ``` -------------------------------- ### Conceptual Diagram: Two Worlds Source: https://github.com/crimx/observable-hooks/blob/main/docs/guide/core-concepts.md Illustrates the conceptual separation between the Observable World and the Normal World, with observable-hooks acting as the bridge. ```text +--------------------------------+ | | | Observable World | | | +--------------------------------+ +------------------+ | observable-hooks | +------------------+ +--------------------------------+ | | | Normal World | | | +--------------------------------+ ``` -------------------------------- ### useObservableRef Source: https://github.com/crimx/observable-hooks/blob/main/docs/api/README.md Returns a mutable ref object and a BehaviorSubject. Whenever ref.current is changed, the BehaviorSubject will emit the new value. Added since v4.2.2. ```APIDOC ## useObservableRef Returns a mutable ref object and a BehaviorSubject. Whenever ref.current is changed, the BehaviorSubject will emit the new value. Added since v4.2.2. **Type parameters:** - `TValue` Ref value type. **Parameters:** Name | Type | Description ------ | ------ | ------ `initialValue` | `TValue` | An optional initial value. **Returns:** `[MutableRefObject, BehaviorSubject]` A tuple of a ref and a BehaviorSubject. **Examples:** ```tsx const Comp = () => { const [elRef, el$] = useObservableRef(null) useSubscription(el$, console.log) return
Content
} ``` ```tsx const Comp = () => { const [ref, value$] = useObservableRef(0) useSubscription(value$, console.log) return } ``` ``` -------------------------------- ### ObservableResource.reload Method Source: https://github.com/crimx/observable-hooks/blob/main/docs/api/suspense.md Cleans up and resubscribes to the input Observable. If the observable is errored, a `newInput$` must be provided. It's recommended to use cold observables for easier reloading. ```typescript reload(newInput$?: Observable): void ``` -------------------------------- ### useSubscription Hook Signature Source: https://github.com/crimx/observable-hooks/blob/main/docs/api/README.md The useSubscription hook accepts an Observable and optional observer callbacks or an observer object. It returns a ref to the RxJS Subscription. ```APIDOC ## useSubscription ### Description Concurrent mode safe Observable subscription. Accepts an Observable and optional `next`, `error`, `complete` functions, or a `PartialObserver` object. These functions must be in the correct order. Use `undefined` or `null` for placeholder. From `v3.2.0`, `useSubscription` accepts an observer object. 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. ### Method Signature ```typescript useSubscription(input$: Observable, observer?: PartialObserver): React.MutableRefObject useSubscription(input$: Observable, next?: function | null | undefined, error?: function | null | undefined, complete?: function | null | undefined): React.MutableRefObject ``` ### Type Parameters - `TInput` Input value within Observable. ### Parameters **Overload 1: Using `PartialObserver`** | Name | Type | Description | | --------- | ---------------------------------- | ----------------------------------------- | | `input$` | `Observable` | Input Observable. | | `observer`| `PartialObserver` | Observer object with next, error, complete. **Overload 2: Using Callbacks** | Name | Type | Description | | -------- | ---------------------------------------------- | ------------------------------------------------ | | `input$` | `Observable | 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 `React.MutableRefObject` A ref object with the RxJS Subscription. The ref `current` is `undefined` on first rendering. ### Examples **Basic Subscription:** ```typescript const subscription = useSubscription(events$, e => console.log(e.type)) ``` **With Complete Callback:** ```typescript const subscription = useSubscription(events$, null, null, () => console.log('complete')) ``` **Accessing Closure Variables:** ```typescript const [debug, setDebug] = useState(false) const subscription = useSubscription(events$, error => { if (debug) { console.log(error) } }) ``` **Using Props Callback:** ```typescript const subscription = useSubscription(events$, props.onEvent) ``` ### Notes - From `v2.0.0`, `useSubscription` will ensure the latest callback is called, allowing safe direct reference to closure variables. - From `v2.3.4`, when the Observable changes, `useSubscription` will automatically unsubscribe the old one and resubscribe to the new one. - From `v3.0.0`, `useSubscription` is concurrent mode safe, preventing observer callbacks from being called from a stale Observable. - Changes of the observer callbacks will not trigger an emission. If this behavior is needed, create another Observable of the callback using `useObservable`. ### Error Handling Due to the design of RxJS, once an error occurs in an observable, it is killed. Consider these options: - Prevent errors from reaching observables or use `catchError` in sub-observables. - Make the observable a state and replace it on error. It will automatically switch to the new one. - From `v4.2.0`, `useRenderThrow` can be used to catch Observable errors with React error boundaries, allowing actions to replace the dead Observable. ``` -------------------------------- ### pluckFirst Source: https://github.com/crimx/observable-hooks/blob/main/docs/api/helpers.md Maps an Observable of Arraylike to an Observable of the first item. ```APIDOC ## pluckFirst ### Description Map an Observable of Arraylike to an Observable of the first item. ### Parameters #### Path Parameters - None #### Query Parameters - None #### Request Body - None ### Parameters - **inputs$** (Observable) - Required - An Observable of arraylike. ### Returns `Observable` Observable of the first item. ### Request Example ```typescript // An Observable of string const text$ = useObservable(pluckFirst, [props.text]) ``` ### Response #### Success Response (200) - None #### Response Example - None ``` -------------------------------- ### Mix Observable Creation and Transformation with useObservable Source: https://github.com/crimx/observable-hooks/blob/main/docs/api/README.md Combines creating an Observable (`isEven$`) with transforming it based on React props using `useObservable`. The `inputs$` observable provides access to the dependency array. ```typescript const enhanced$ = useObservable( inputs$ => isEven$.pipe( withLatestFrom(inputs$), map(([isEven, [isOpen]]) => isEven && isOpen) ), [props.isOpen] ) ``` -------------------------------- ### useObservableState Hook Source: https://github.com/crimx/observable-hooks/blob/main/docs/api/README.md Manages state using an observable stream of actions and an initial state. Dispatches actions to update the state based on the provided reducer logic. ```javascript 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 }) ``` -------------------------------- ### useObservableRef Hook Signatures Source: https://github.com/crimx/observable-hooks/blob/main/docs/api/README.md These are the type signatures for the useObservableRef hook, showing its flexibility with initial values and return types. ```typescript useObservableRef( initialValue: TValue ): [MutableRefObject, BehaviorSubject] useObservableRef( initialValue: TValue | null ): [RefObject, BehaviorSubject] useObservableRef( initialValue?: TValue ): [MutableRefObject, BehaviorSubject] ``` -------------------------------- ### useObservableSuspense Hook Source: https://github.com/crimx/observable-hooks/blob/main/docs/api/suspense.md Consumes an ObservableResource, enabling React components to seamlessly integrate with RxJS observables within a Suspense-enabled concurrent mode environment. ```APIDOC ## useObservableSuspense(resource: ObservableResource): TOutput Consume the Observable resource. Unlike Promise, Observable implements multiple push protocol. This hook triggers necessary re-rendering when Suspense should restart. **Type parameters:** - `TInput` Value type of the input Observable. - `TOutput` Resulted resource value. Default TInput. **Parameters:** Name | Type | Description ------ | ------ | ------ `resource` | `ObservableResource` | Observable resource. **Returns:** `TOutput` resource value. ``` -------------------------------- ### ObservableResource Class Source: https://github.com/crimx/observable-hooks/blob/main/docs/api/suspense.md Rewires an Observable to a Relay-like Suspense resource, enabling concurrent mode safe usage with React Suspense. ```APIDOC ## Class ObservableResource Rewires Observable to Relay-like Suspense resource. **Type parameters:** - `TInput` Value type of the input Observable. - `TOutput` Resulted resource value. Default TInput. ### Constructor ```typescript new ObservableResource( input$: Observable, isSuccess?: (value: TInput) => value is TOutput ): ObservableResource ``` **Parameters:** Name | Type | Description ------ | ------ | ------ `input$` | `Observable` | An Observable. `isSuccess` | `(value: TInput): value is TOutput` | Optional function that determines if the value emitted from `input$` is of success state. If false a Suspense is triggered. Default all true. ### Public Properties Name | Type | Description ------ | ------ | ------ `shouldUpdate$$` | `Subject` | Emit when the Component needs extra rerendering. ### Public Methods Name | Type | Description ------ | ------ | ------ `read` | `(): TOutput` | Return cached value on success state. Throw suspender on pending state. Throw error on error state. `destroy` | `(): void` | UnSubscribe input Observable. `reload` | `(newInput$?: Observable): void` | Clean up and resubscribe input Observable. For errored hot observable there is no way to resubscribe hence a `newInput$` must be provided. Otherwise if omitted the original observable will be resubscribed. It is recommended to use cold observable if possible for easy reloading. (Also for those who are unfamiliar with observable temperature, even though a `Subject` is hot, `subject.pipe(...)` is cold.) ``` -------------------------------- ### Debounced Text Verification with RxJS Source: https://github.com/crimx/observable-hooks/blob/main/docs/examples/README.md Implement debounced input validation using `useObservableState` and RxJS operators like `debounceTime` and `withLatestFrom`. This snippet verifies text input asynchronously after a specified delay, providing real-time feedback on validity. ```jsx import React from 'react' import PropTypes from 'prop-types' import { withLatestFrom, switchMap, debounceTime, pluck } from 'rxjs/operators' import { useObservable, useObservableState, pluckFirst } from 'observable-hooks' const checkText = (text, uuid) => fetch(`https://api/${text}?uuid=${uuid}`) .then(response => response.ok) .catch(() => false) export const App = props => { // `pluckFirst` is a simple helper function to avoid garbage collection, // equivalent to `inputs$ => inputs$.pipe(map(inputs => inputs[0]))` const uuid$ = useObservable(pluckFirst, [props.uuid]) const [isValid, onChange] = useObservableState( event$ => event$.pipe( // React synthetic event object will be reused. // Pluck the value out first. pluck('currentTarget', 'value'), debounceTime(400), withLatestFrom(uuid$), switchMap(([text, uuid]) => checkText(text, uuid)) ), false ) return ( <> ) } App.propTypes = { uuid: PropTypes.string } ```