UNPKG

react-async-iterators

Version:

The magic of JavaScript async iterators in React ⛓️ 🧬 🔃

165 lines (163 loc) 7.3 kB
import { type MaybeFunction } from '../common/MaybeFunction.js'; import { type AsyncIterableSubject } from '../AsyncIterableSubject/index.js'; export { useAsyncIter, type IterationResult }; /** * `useAsyncIter` hooks up a single async iterable value to your component and its lifecycle. * * @example * ```tsx * import { useAsyncIter } from 'react-async-iterators'; * * function SelfUpdatingTodoList(props) { * const { value: todos } = useAsyncIter(props.todosAsyncIter); * return ( * <ul> * {todos?.map(todo => ( * <li key={todo.id}>{todo.text}</li> * ))} * </ul> * ); * } * ``` * * --- * * Given an async iterable `input`, this hook will iterate it value-by-value and update (re-render) the * host component upon each yielded value, along with any possible completion or error it may run into. * `input` may also be given a plain (non async iterable) value, in which case it will simply be used * to render once and immediately, thus enabling components that can handle _"static"_ as well as * _"changing"_ values and props seamlessly. * * The hook initializes and maintains its iteration process with its given async iterable `input` * across component updates as long as `input` keeps getting passed the same object reference every * time (similar to the behavior of a `useEffect(() => {...}, [input])`), therefore care should be taken * to avoid constantly recreating the iterable on every render, by e.g; declaring it outside the component * body, controlling __when__ it should be recreated with React's * [`useMemo`](https://react.dev/reference/react/useMemo) or alternatively use the library's * {@link iterateFormatted `iterateFormatted`} util for a formatted version of an iterable which * preserves its identity. * Whenever `useAsyncIter` detects a different `input` value, it automatically closes the previous * active async iterable and proceeds to start iteration with the new `input` async iterable. On * component unmount, the hook will also ensure closing any currently iterated `input`. * * The object returned from `useAsyncIter` holds all the state from the most recent iteration * of `input` (most recent value, whether is completed or still running, etc. - see * {@link IterationResult `IterationResult`}). * In case `input` is given a plain value, it will be delivered as-is within the returned * result object's `value` property. * * --- * * @template TVal The type of values yielded by the passed iterable or type of plain value if otherwise passed. * @template TInitVal The type of the initial value, defaults to `undefined`. * * @param input Any async iterable or plain value. * @param initialVal Any optional starting value for the hook to return prior to the ___first yield___ of the ___first given___ async iterable, defaults to `undefined`. You can pass an actual value, or a function that returns a value (which the hook will call once during mounting). * * @returns An object with properties reflecting the current state of the iterated async iterable or plain value provided via `input` (see {@link IterationResult `IterationResult`}). * * @see {@link IterationResult `IterationResult`} * * @example * ```tsx * // With an `initialVal` and showing usage of all properties of the returned iteration object: * * import { useAsyncIter } from 'react-async-iterators'; * function SelfUpdatingTodoList(props) { * const todosNext = useAsyncIter(props.todosAsyncIter, []); * return ( * <> * {todosNext.error ? ( * <div>An error was encountered: {todosNext.error.toString()}</div> * ) : todosNext.done && ( * <div>No additional updates for todos are expected</div> * )} * * {todosNext.pendingFirst ? ( * <div>Loading first todos...</div> * ) : ( * <ul> * {todosNext.map(todo => ( * <li key={todo.id}>{todo.text}</li> * ))} * </ul> * )} * </> * ); * } * ``` */ declare const useAsyncIter: { <TVal>(input: TVal, initialVal?: undefined): IterationResult<TVal>; <TVal, TInitVal>(input: TVal, initialVal: MaybeFunction<TInitVal>): IterationResult<TVal, TInitVal>; }; /** * The `iterationResult` object holds all the state from the most recent iteration of a currently * hooked async iterable object (or plain value). * * Returned from the {@link useAsyncIter `useAsyncIter`} hook and also injected into * {@link Iterate `<Iterate>`} component's render function. * * @see {@link useAsyncIter `useAsyncIter`} * @see {@link Iterate `<Iterate>`} */ type IterationResult<TVal, TInitVal = undefined> = { /** * The most recent value received from the async iterable iteration, starting as {@link TInitVal}. * If the source was a plain value instead, it will simply be it, ignoring any {@link TInitVal}. * * When the source iterable changes and an iteration restarts with a new iterable, the same last * `value` is carried over and reflected until the new iterable resolves its first value. * * If the async iteration has completed or errored out, `value` would still hold the last * value it picked up but now the accompanying `done` and `error` will be set accordingly. * */ value: TVal extends AsyncIterableSubject<infer J> ? J : TVal extends AsyncIterable<infer J> ? J | TInitVal : TVal; /** * Indicates whether the iterated async iterable is still pending its own first value to be * resolved. * Will appear `false` for any iterations thereafter and reset back every time the iteratee * is changed to a new one. * * Can be used for displaying _"loading" states_ in many cases, metaphorically similar to * a how promise's pending state is thought of. * * Is always `false` for any plain value given instead of an async iterable. */ pendingFirst: boolean; /** * Indicates whether the iterated async iterable has ended having no further values to yield, * meaning either of: * - it has completed (by resolving a `{ done: true }` object * ([MDN docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#done))) * - it had thrown an error (in which case the escorting `error` property will be set to * that error). * * When `true`, the adjacent `value` property will __still be set__ to the last value seen * before the moment of completing/erroring. * * Is always `false` for any plain value given instead of an async iterable. */ done: boolean; /** * Indicates whether the iterated async iterable threw an error, capturing a reference to it. * * If `error` is non-empty, the escorting `done` property will always be `true` since the * iteration process is considered over. * * Is always `undefined` for any plain value given instead of an async iterable. */ error: unknown; } & ((TVal extends AsyncIterableSubject<unknown> ? never : TVal extends AsyncIterable<unknown> ? { pendingFirst: true; done: false; error: undefined; } : never) | ({ pendingFirst: false; } & ({ done: false; error: undefined; } | { done: true; error: unknown; })));