Skip to content

Commit b1568b5

Browse files
authored
feat: new useSharedAsyncIter hook (#87)
* implmentations and tests for a new `useAsyncIterMemo` hook * implementations and tests for a new `useAsyncIterMemo` hook * more done * fix test * up * up * code for a new `useSharedAsyncIter` hook * add JSDocs * add section in readme * make the hooks returned iter type more concrete with a non-optional `.return` method and more
1 parent e5e94dd commit b1568b5

File tree

12 files changed

+759
-5
lines changed

12 files changed

+759
-5
lines changed

README.md

Lines changed: 95 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ Async iterables/iterators are a native language construct in JS that can be view
2626

2727
Somewhat obvious to say, the React ecosystem features many methods and tools that have to do with integrating promise-based data into your React components; from higher level SDK libraries, state managers - to generic async utilities, which make the different promise states available to the rendering. And just like that - `react-async-iterators` packs hooks, components and utilities written in TypeScript with the aim to make async iterables into __first-class citizens to React__ as they become gradually more prevalent across JavaScript platforms.
2828

29-
What can `react-async-iterators` be used for?
29+
## What can `react-async-iterators` be used for?
3030

3131
- easily consuming async iterables obtained from any library, web API or composed manually - in a React-friendly declarative fashion.
3232
<!-- ...Dynamically plug and unplug them at any place across your app's component tree with automatic teardown... -->
@@ -137,6 +137,7 @@ function LiveUserProfile(props: { userId: string }) {
137137
- [`useAsyncIter`](#useasynciter)
138138
- [`useAsyncIterMulti`](#useasyncitermulti)
139139
- [`useAsyncIterState`](#useasynciterstate)
140+
- [`useSharedAsyncIter`](#usesharedasynciter)
140141
- [Utils](#utils)
141142
- [`iterateFormatted`](#iterateformatted)
142143
- [License](#license)
@@ -1010,8 +1011,12 @@ const [nextNum, nextStr, nextArr] = useAsyncIterMulti([numberIter, stringIter, a
10101011

10111012
### Returns
10121013

1014+
<ul>
1015+
10131016
An array of objects with up-to-date information about each input's current value, completion status, and more - corresponding to the order by which they appear on `values` (see [Iteration state properties breakdown](#iteration-state-properties-breakdown)).
10141017

1018+
</ul>
1019+
10151020
### Notes
10161021

10171022
-
@@ -1166,7 +1171,11 @@ function handleValueSubmit() {
11661171

11671172
### Returns
11681173

1169-
A stateful async iterable with accessible current value and a function for yielding an update. The returned async iterable is a shared iterable such that multiple simultaneous consumers (e.g multiple [`<It>`](#it)s) all pick up the same yields at the same times. The setter function, like[`React.useState`'s setter](https://react.dev/reference/react/useState#setstate), can be provided the next state directly, or a function that calculates it from the previous state.
1174+
<ul>
1175+
1176+
A stateful async iterable with accessible current value and a function for yielding an update. The returned async iterable is a shared iterable such that multiple simultaneous consumers (e.g multiple [`<It>`](#it)s) all pick up the same yields at the same times. The setter function, like[`React.useState`'s setter](https://react.dev/reference/react/useState#setstate), can be provided either the next state directly, or a function that calculates it from the previous state.
1177+
1178+
</ul>
11701179

11711180
### Notes
11721181

@@ -1232,6 +1241,89 @@ function handleValueSubmit() {
12321241

12331242

12341243

1244+
### useSharedAsyncIter
1245+
1246+
Hook that takes a source async iterable and returns a version of it that will always initialize up to
1247+
just one single instance of the source at any point in time, sharing it to any number of simultaneous consumers
1248+
the result iterable might have (e.g multiple [`<It>`](#it)s).
1249+
1250+
```tsx
1251+
const sharedIter = useSharedAsyncIter(iter);
1252+
// ...
1253+
```
1254+
1255+
Any number of iterators for the resulting iterable you create and consume simultaneously will only ever
1256+
create a single iterator internally for the original source and distribute every yielded value, completion or
1257+
possible error among each of them.
1258+
1259+
In a _reference-counting_ fashion, only when the last remaining iterator is closed will the shared
1260+
source iterator be finally closed as well, disposing of resources it held, after which instantiating a new iterator will restart the cycle. This way, async iterables that instantiate server connections, streams, etc. - can easily be consumed or rendered concurrently by multiple components without possibly opening duplicate resources or other undesired effects, depending on the way these source iterables were constructed.
1261+
1262+
If given a plain non-iterable value, this hook would seamlessly return it as-is without additional effect.
1263+
1264+
### Parameters
1265+
1266+
- `value`:
1267+
The source async iterable or plain value.
1268+
1269+
### Returns
1270+
1271+
<ul>
1272+
1273+
A "_shared_" version of the source async iterable or the source value itself in case it was a plain value.
1274+
1275+
</ul>
1276+
1277+
### Notes
1278+
1279+
<ul>
1280+
1281+
> <br/>ℹ️ Repeated calls with the same source iterable will return the same memoized result iterable, as well as calls with [`iterateFormatted`](#iterateformatted)-returned iterables based of the same source for that matter.<br/><br/>
1282+
1283+
</ul>
1284+
1285+
<details>
1286+
<summary><b><i>Additional examples</i></b></summary>
1287+
<br/>
1288+
<ul>
1289+
1290+
```tsx
1291+
import { useSharedAsyncIter, It } from 'react-async-iterators';
1292+
1293+
function MyComponent(props) {
1294+
const messagesIter = useSharedAsyncIter(props.messagesIter);
1295+
1296+
return (
1297+
<div>
1298+
Number of unread messages:
1299+
<It value={messagesIter}>
1300+
{next => (
1301+
next.value?.filter(msg => msg.isRead).length ?? 0
1302+
)}
1303+
</It>
1304+
1305+
Message list:
1306+
<It value={messagesIter}>
1307+
{next => (
1308+
next.value?.map(msg => (
1309+
<div>
1310+
From: {msg.from},
1311+
Date: {msg.date},
1312+
Was read: {msg.isRead ? 'Y' : 'N'}
1313+
</div>
1314+
))
1315+
)}
1316+
</It>
1317+
</div>
1318+
);
1319+
}
1320+
```
1321+
1322+
</ul>
1323+
</details>
1324+
1325+
1326+
12351327
## Utils
12361328

12371329

@@ -1266,7 +1358,7 @@ iterateFormatted(myIter, (value, idx) => {
12661358
Any async iterable or plain value.
12671359

12681360
- `formatFn`:
1269-
Function that performs formatting/mapping logic for each value of `source`.
1361+
Function that performs formatting/mapping logic for each value of `source`. It is called with current value and the iteration index as arguments.
12701362

12711363
### Returns
12721364

spec/tests/useAsyncIterState.spec.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,7 @@ describe('`useAsyncIterState` hook', () => {
283283

284284
it(
285285
gray(
286-
"The returned iterable's values are each shared between all its parallel consumers so that each will receives all values that will yield from the time it started consuming"
286+
"The returned iterable's values are each shared between all its parallel consumers so that each will receive every value that will yield from the time it started consuming"
287287
),
288288
async () => {
289289
const [values, setValue] = renderHook(() => useAsyncIterState<string>()).result.current;

0 commit comments

Comments
 (0)