Skip to content

Commit f19eabd

Browse files
authored
feat: implement a new useAsyncIterEffect hook (#98)
1 parent bc3e057 commit f19eabd

File tree

11 files changed

+1154
-9
lines changed

11 files changed

+1154
-9
lines changed

README.md

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@ function LiveUserProfile(props: { userId: string }) {
159159
- [Hooks](#hooks)
160160
- [`useAsyncIter`](#useasynciter)
161161
- [`useAsyncIterMulti`](#useasyncitermulti)
162+
- [`useAsyncIterEffect`](#useasyncitereffect)
162163
- [`useAsyncIterState`](#useasynciterstate)
163164
- [`useSharedAsyncIter`](#usesharedasynciter)
164165
- [Utils](#utils)
@@ -1210,6 +1211,76 @@ const [nextNum, nextStr, nextArr] = useAsyncIterMulti([numberIter, stringIter, a
12101211

12111212

12121213

1214+
### `useAsyncIterEffect`
1215+
1216+
Given some async iterables, a side-effect function and a computed list of dependencies - runs the provided side-effect whenever any of the provided dependencies change from the previously seen ones, letting you derive them from the values yielded by the async iterables.
1217+
1218+
This hook is like an _async-iterable-aware_ version for [`React.useEffect`](https://react.dev/reference/react/useEffect), allowing dependencies to be also computed from values yielded by the given async iterables each time, and letting the effect fire directly in reaction to particular async iterable yields rather than only just component scope values being changed across re-renders (as does the classic [`React.useEffect`](https://react.dev/reference/react/useEffect)).
1219+
1220+
```tsx
1221+
useAsyncIterEffect(
1222+
[fooIter, barIter],
1223+
(foo, bar) => [
1224+
() => {
1225+
runMyEffect(foo.value, bar.value, otherValue);
1226+
},
1227+
[foo.value, bar.value, otherValue],
1228+
]
1229+
);
1230+
1231+
// Or if returning an effect destructor function:
1232+
useAsyncIterEffect(
1233+
[fooIter, barIter],
1234+
(foo, bar) => [
1235+
() => {
1236+
runMyEffect(foo.value, bar.value, otherValue);
1237+
return () => {
1238+
cancelMyEffect();
1239+
}
1240+
},
1241+
[foo.value, bar.value, otherValue],
1242+
]
1243+
);
1244+
```
1245+
1246+
This hook is a consuming hook; any given item on the base deps array (first argument) that is async iterable will immediately start being iterated internally and continue for as long as its underlying iterable remains present in the array. Like most other hooks - plain (non async iterable) values can also be provided within the base deps at any time be conveyed as if are immediate, singular yields.
1247+
1248+
Whenever either of following events occur;
1249+
1250+
- Any of the base deps yields a value
1251+
- Hook is called again due to component re-render
1252+
1253+
-> the hook will call the effect resolver function (second argument) again, providing all the last states of the actively iterated items as individual arguments corresponding to their order within the base deps array. From there, you use it exactly like [`React.useEffect`](https://react.dev/reference/react/useEffect) while having the last yields accesible to use for your actual effect dependencies and/or your effect function's logic itself. The hook supports returning from the effect function an optional function to serve as the effect tear down/destructor, like the original [`React.useEffect`](https://react.dev/reference/react/useEffect).
1254+
1255+
### Parameters
1256+
1257+
- `baseDeps`:
1258+
An array of zero or more async iterable or plain values (mixable). In response to their yields, effect dependencies will re-evaluate and possibly fire the effect.
1259+
1260+
- `effectResolverFn`:
1261+
A user-provided function to be called by the hook whenever any yield occurres, getting the last states of all the actively iterated base deps as arguments. It should return a tuple with the effect function as the first item (_required_) and the next array of dependencies as the second (_optional_). The effect function may _optionally_ itself return a function to serve as a effect teardown/destructor.
1262+
1263+
1264+
### Returns
1265+
1266+
<ul>
1267+
1268+
_Nothing_
1269+
1270+
</ul>
1271+
1272+
### Notes
1273+
1274+
<ul>
1275+
1276+
> <br/>ℹ️ While you may optionally omit the dependency array in the effect resolver function's returned tuple as mentioned, note that this produces a similar behavior to calling [`React.useEffect`](https://react.dev/reference/react/useEffect) with dependencies omitted - the effect will be fired __on every re-render__ and __on every yield__ by the async iterable base dependencies.<br/><br/>
1277+
1278+
> <br/>ℹ️ It's important to remember that when using [`useAsyncIterEffect`](#useasyncitereffect) and [`<It>`](#it) to operate on the same async iterable, whether across components or within the same one - each of these two consumers would individually attempt to obtain an iterator from the same source iterable. Depending on how the source iterable's implementation it could lead to duplicate resources (e.g. WebSocket connections) being started. If this is undesirable, just ensure to pass that source iterable through a [`useSharedAsyncIter`](#usesharedasynciter) call anywhere along its route, ___before___ it encounters any consuming hooks like the latter ones.<br/><br/>
1279+
1280+
</ul>
1281+
1282+
1283+
12131284
### `useAsyncIterState`
12141285

12151286
Basically like [`React.useState`](https://react.dev/reference/react/useState), only that the value is provided back __wrapped in an async iterable__.

spec/tests/Iterate.spec.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -857,7 +857,7 @@ describe('`Iterate` component', () => {
857857

858858
it(
859859
gray(
860-
'When given a `ReactAsyncIterable` yielding `undefined`s or `null`s that wraps an iter which originally yields non-nullable values, processes the `undefined`s and `null` values expected'
860+
'When given a formatted iterable yielding `undefined`s or `null`s that wraps a source iter yielding non-null values, processes the `undefined`s and `null` values as expected'
861861
),
862862
async () => {
863863
const channel = new IteratorChannelTestHelper<string>();

spec/tests/useAsyncIter.spec.ts

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

620620
it(
621621
gray(
622-
'When given a `ReactAsyncIterable` yielding `undefined`s or `null`s that wraps an iter which originally yields non-nullable values, returns the `undefined`s and `null`s in the result as expected (https://github.com/shtaif/react-async-iterators/pull/32)'
622+
'When given a formatted iterable yielding `undefined`s or `null`s that wraps a source iter yielding non-null values, returns the `undefined`s and `null`s in the result as expected (https://github.com/shtaif/react-async-iterators/pull/32)'
623623
),
624624
async () => {
625625
const channel = new IteratorChannelTestHelper<string>();

0 commit comments

Comments
 (0)