> Hooks, components and utilities for working with JavaScript async iterator values in React.js.
    
A React.js library that makes it __easy and satisfying__ to integrate and render JS async iterators across and throughout your app's components. Expanding from that, it enables you to describe and propagate states and various aspects of your app in actual async iterator form, tapping into the full benefits and flexibility of this JS construct.
The goal behind this library is to promote a mental model where fundamentally every piece of data in a JavaScript program can be expressed in either a plain and static form, or in a ___dynamic, self-evolving form - an async iterable___. By simply wrapping a value in an async iterator or iterable, it becomes a self-updating entity while remaining first-class data. From this, it follows naturally that every interface should and could accommodate either kind of inputs, seamlessly adapting to changes over time as intuitively expected.
To facilitate this, react-async-iterators offers a set of tools specifically tailored for the frontend and React which and embraces composability with the upcoming standardization of Async Iterator Helpers proposal as well as projects such as iter-tools, IxJS and more.
$3

``tsx import { It } from 'react-async-iterators';
const randoms = { async *[Symbol.asyncIterator]() { while (true) { await new Promise(r => setTimeout(r, 500)); const x = Math.random(); yield Math.round(x * 10); } }, };
Async iterables and iterators are a native JavaScript construct that can be seen as a counterpart to
Promises in a way that is captured by the following:
> _A promise resolves __a single__ value asynchronously, whereas an async iterable is a stream yielding __any number__ of values asynchronously._
Slightly obvious to say, the React ecosystem is featuring many methods and tools that have to do with integration of promise-based data into your React components; from higher level SDK libraries, state managers - to generic async utilities, which make the different promise states accessible during render. And to the same extent,
react-async-iterators packs hooks, components and utilities, written in 100% TypeScript, with the aim to make async iterables into __first-class citizens to React__ as they become gradually more prevalent across the JavaScript ecosystem.
Who is
react-async-iterators for?react-async-iterators is designed for React developers aiming to seamlessly integrate asynchronous data streams into their apps, as well as enhance and optimize how they build their interactive data-driven apps in general. This library offers a declarative approach to manage real-time updates, push or pull based data sources, or any asynchronous series of values easily and effectively within React components.
When should you use
react-async-iterators?
- When integrating any async iterable obtained from a library, a web API, or composed manually.
- When apps involve any _asynchronously-generated series_ of data, such as data updated via recurring timers, WebSocket messages, GraphQL subscriptions, Geolocation watching and more...
- When rendering a complex form or dynamic widget with large nested component tree for which updates might impact UI performance.
What can
react-async-iterators do?
- Easily render any async iterable in a declarative, React-friendly style.
- Convert any series of data into an async iterable, enabling it the full functionality of this library.
- Unlock new patterns for expressing data flow within and between components which greatly minimize redundant re-renders by embracing async iterables __as data__.
- Compose and refine your data in the form of async iterables, enabling specialized behaviors and optimizations in propagating your data which are otherwise very hard to achieve in a typical React environment.
- Build better apps and components by relying on async iterables' consistent semantics for completion and error, composability and resource encapsulation. Handle any async sequence of values perceivable via a single generic interface; ___the native one___, instead of grasping various methods and opinionated APIs coupled to every type of operation.
Installation
`sh
With npm:
npm i react-async-iterators
With pnpm:
pnpm i react-async-iterators
With Yarn:
yarn add react-async-iterators `
Can then be imported as follows (TypeScript/ESM style):
`ts import { It, type IterationResult } from 'react-async-iterators'; `
Overview
Consuming async iterables
Async iterables can be hooked into your components and consumed using
and , or their hook counterparts useAsyncIter and useAsyncIterMulti respectively.
The iteration values and states are expressed via a consistent structure (see exaustive list in this breakdown).
They may be accessed as follows:
`tsx const myIter = getSomeIter(); // given some myIter async iterable `
With
:`tsx import { It } from 'react-async-iterators';
{next => { next.pendingFirst; /* -> whether we're still waiting for the first value yielded from
myIter, analogous to a promise's pending state. */
next.value; /* -> the most recent value yielded. If
pendingFirst is true, we should see the last value carried over from the previous iterable before myIter (otherwise fall back to "first_value" if we've just been mounted) */
next.done; /* -> whether the iteration of
myIter has finished (will yield no further values) */
next.error; /* -> if the iterated async iterable threw an error, this will be set to it along with done showing true */ }}
`
With
useAsyncIter:`tsx import { useAsyncIter } from 'react-async-iterators';
const next = useAsyncIter(myIter, 'first_value');
// (Properties are identical to the above...) next.pendingFirst; next.value; next.done; next.error;
`
_Using the component form may be __typically preferrable__ over the hook form_ (e.g
over useAsyncIter) - Why? because using it, when changes in data occure - the re-rendered UI area within a component tree can be declaratively narrowed to the necessary minimum, saving other React elements that do not depend on its values from re-evaluation. On the the other hand - useAsyncIter, being a hook, must re-render the entirety of the host component's output for every new value.
When segregating different flows of data the components' render code like this, using the component forms - it makes for a more managable code, and might get rid of having to akwardly split components down to smaller parts just to render-optimize them when it otherwise wouldn't "feel right" to do so.
Plain values
All of the consuming hooks and components __also__ accept plain (_"non-iterable"_) values safely, rendering them as-are with very low extra overhead - their inputs may alternate between async iterable and plain values any time.
When rendering a plain value, the iteration state properties behave alternatively like so:
-
.value reflects the plain value as-is - .pendingFirst and .done are ALWAYS false - .error is ALWAYS empty
> ā¹ļø When providing a plain value right upon mounting - the initial value, if given, is ignored.
_Showing
being used with either plain or async iterable values, wrapped as a custom component:_`tsx import { It, type MaybeAsyncIterable } from 'react-async-iterators';
One use for this among others is an ability for a certain async-iterable-fed UI piece to be pushed some alternative "placeholder" value at times there isn't an actual async iterable available to feed it with.
Another implication of this conveniency is the possibility to design apps and component libraries that can receive data expressable in both _"static"_ and _"changing"_ fashions - seamlessly. If a certain component has to be given a string value prop, but you happen to (or wish to) only have an ___async iterable of strings___ at hand - why shouldn't you be able to ___pass just that___ onto the same prop and it would just work as expected - self updating whenever the next string is yielded? Async iterables are standard JavaScript after all.
Iteration lifecycle
When rendering an async iterable with any of the consuming component/hooks, they immediately begin iterating through it value-by-value.
The current active iteration is always associated to the particular value that was given into the consumer component or hook, such that re-rendering the consumer again and again with a reference to the same object will keep the same active iteration running persistingly in a React-like fashion (similar to
React.useEffect not re-running until its dependencies are changed).
Whenever the consumer receives a _new_ value to iterate, it will immediately dispose of any current running iteration (calling
.return() on its held iterator) and proceed iterating the new value in the same manner described.
Finally, when the consumer is unmounted, any current running iteration is disposed of as well.
$3
The following phases and state properties are reflected via all consumer utilities (with hooks - returned, with components - injected to their given render functions):
with error property being non-empty - __ending due to source throwing an error__ or
with error property being undefined - __ending due to completion - source is done__.
š _Repeat on change of source value_ š
Async iterables with current values
Throughout the library there is a specially recognized case (or convention) for expressing async iterables with a notion of a _"current value"_. These are simply defined as any regular async iterable object coupled with a readable
.value.current property.
When any consumer hook/component from the library detects the presence of a current value (
.value.current) on an async iterable, it can render it immediately and skip the isPending: truephase, since it effectively signals there is no need to _wait_ for a first yield - the value is available already.
This rule bridges the gap between async iterables which always yield asynchronously (as their yields are wrapped in promises) and React's component model in which render outputs are strictly synchronous. Due to this discrepency, for example, if the first value for an async iterable is known in advance and yielded as soon as possible - React could only grab the yielded value from it via a subsequent (immediate) run/render of the consumer hook/component (since the promise can resolve only _after_ such initial sync run/render). This issue is therefore solved by async iterables that expose a current value.
For example, the stateful iterable created from the
useAsyncIterState hook (_see State as an async iterable_) applies this convention from its design, acting like a "topic" with an always-available current value that's able to signal out future changes, skipping pending phases, so there's no need to set initial starting states.
Formatting values
When building your app with components accepting async iterable data as props, as you render these and have to provide such props - you may commonly see a need to _re-format_ held async iterables' value shapes before they're passed in those props, in order for them to match the expected shape.
iterateFormatted is an easy-to-use approach to many cases like this.
For instance, let's say we're trying to use some existing