Skip to content

Commit 6e1a36b

Browse files
authored
feat(useAsyncIterState): add a current value property on useAsyncIterState hook's returned iterable (#41)
1 parent 18997f8 commit 6e1a36b

File tree

3 files changed

+46
-19
lines changed

3 files changed

+46
-19
lines changed

spec/tests/useAsyncIterState.spec.tsx

+20-3
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,17 @@ describe('`useAsyncIterState` hook', () => {
1919
const valuesToSet = ['a', 'b', 'c'];
2020

2121
const collectPromise = pipe(values, asyncIterTake(valuesToSet.length), asyncIterToArray);
22+
const currentValues = [values.value.current];
2223

2324
for (const value of valuesToSet) {
24-
await act(() => setValue(value));
25+
await act(() => {
26+
setValue(value);
27+
currentValues.push(values.value.current);
28+
});
2529
}
2630

2731
expect(await collectPromise).toStrictEqual(['a', 'b', 'c']);
32+
expect(currentValues).toStrictEqual([undefined, 'a', 'b', 'c']);
2833
});
2934

3035
it(
@@ -79,6 +84,7 @@ describe('`useAsyncIterState` hook', () => {
7984

8085
const collections = await Promise.all([collectPromise1, collectPromise2]);
8186
expect(collections).toStrictEqual([[], []]);
87+
expect(values.value.current).toStrictEqual(undefined);
8288
}
8389
);
8490

@@ -91,13 +97,18 @@ describe('`useAsyncIterState` hook', () => {
9197
const [values, setValue] = renderedHook.result.current;
9298

9399
const [collectPromise1, collectPromise2] = range(2).map(() => asyncIterToArray(values));
100+
const currentValues = [values.value.current];
94101

95-
await act(() => setValue('a'));
102+
await act(() => {
103+
setValue('a');
104+
currentValues.push(values.value.current);
105+
});
96106

97107
renderedHook.unmount();
98108

99109
const collections = await Promise.all([collectPromise1, collectPromise2]);
100110
expect(collections).toStrictEqual([['a'], ['a']]);
111+
expect(currentValues).toStrictEqual([undefined, 'a']);
101112
}
102113
);
103114

@@ -113,6 +124,7 @@ describe('`useAsyncIterState` hook', () => {
113124

114125
const collections = await Promise.all(range(2).map(() => asyncIterToArray(values)));
115126
expect(collections).toStrictEqual([[], []]);
127+
expect(values.value.current).toStrictEqual(undefined);
116128
}
117129
);
118130

@@ -124,16 +136,21 @@ describe('`useAsyncIterState` hook', () => {
124136
const [values, setValue] = renderHook(() => useAsyncIterState<string>()).result.current;
125137

126138
const consumeStacks: string[][] = [];
139+
const currentValues = [values.value.current];
127140

128141
for (const [i, value] of ['a', 'b', 'c'].entries()) {
129142
consumeStacks[i] = [];
130143
(async () => {
131144
for await (const v of values) consumeStacks[i].push(v);
132145
})();
133-
await act(() => setValue(value));
146+
await act(() => {
147+
setValue(value);
148+
currentValues.push(values.value.current);
149+
});
134150
}
135151

136152
expect(consumeStacks).toStrictEqual([['a', 'b', 'c'], ['b', 'c'], ['c']]);
153+
expect(currentValues).toStrictEqual([undefined, 'a', 'b', 'c']);
137154
}
138155
);
139156
});
+19-12
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
1+
import { type MutableRefObject } from 'react';
12
import { promiseWithResolvers } from '../common/promiseWithResolvers.js';
23

3-
export { IterableChannel };
4+
export { IterableChannel, type AsyncIterableSubject };
45

5-
class IterableChannel<TVal> {
6+
class IterableChannel<T> {
67
#isClosed = false;
7-
#nextIteration = promiseWithResolvers<IteratorResult<TVal, void>>();
8+
#nextIteration = promiseWithResolvers<IteratorResult<T, void>>();
89

9-
put(value: TVal): void {
10+
put(value: T): void {
1011
if (!this.#isClosed) {
12+
this.values.value.current = value;
1113
this.#nextIteration.resolve({ done: false, value });
1214
this.#nextIteration = promiseWithResolvers();
1315
}
@@ -18,20 +20,17 @@ class IterableChannel<TVal> {
1820
this.#nextIteration.resolve({ done: true, value: undefined });
1921
}
2022

21-
iterable: {
22-
[Symbol.asyncIterator](): {
23-
next(): Promise<IteratorResult<TVal, void>>;
24-
return(): Promise<IteratorReturnResult<void>>;
25-
};
26-
} = {
23+
values: AsyncIterableSubject<T> = {
24+
value: {
25+
current: undefined,
26+
},
27+
2728
[Symbol.asyncIterator]: () => {
2829
const whenIteratorClosed = promiseWithResolvers<IteratorReturnResult<undefined>>();
29-
3030
return {
3131
next: () => {
3232
return Promise.race([this.#nextIteration.promise, whenIteratorClosed.promise]);
3333
},
34-
3534
return: async () => {
3635
whenIteratorClosed.resolve({ done: true, value: undefined });
3736
return { done: true, value: undefined };
@@ -40,3 +39,11 @@ class IterableChannel<TVal> {
4039
},
4140
};
4241
}
42+
43+
type AsyncIterableSubject<T> = {
44+
value: MutableRefObject<T | undefined>;
45+
[Symbol.asyncIterator](): {
46+
next(): Promise<IteratorResult<T, void>>;
47+
return(): Promise<IteratorReturnResult<void>>;
48+
};
49+
};

src/useAsyncIterState/index.ts

+7-4
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { useEffect, useRef } from 'react';
2-
import { IterableChannel } from './IterableChannel.js';
2+
import { IterableChannel, type AsyncIterableSubject } from './IterableChannel.js';
33
import { type Iterate } from '../Iterate/index.js'; // eslint-disable-line @typescript-eslint/no-unused-vars
44

5-
export { useAsyncIterState, type AsyncIterStateResult };
5+
export { useAsyncIterState, type AsyncIterStateResult, type AsyncIterableSubject };
66

77
/**
88
* Basically like {@link https://react.dev/reference/react/useState `React.useState`}, only that the value
@@ -58,7 +58,7 @@ function useAsyncIterState<TVal>(): AsyncIterStateResult<TVal> {
5858
const channel = new IterableChannel<TVal>();
5959
return {
6060
channel,
61-
result: [channel.iterable, newVal => channel.put(newVal)],
61+
result: [channel.values, newVal => channel.put(newVal)],
6262
};
6363
})();
6464

@@ -77,4 +77,7 @@ function useAsyncIterState<TVal>(): AsyncIterStateResult<TVal> {
7777
*
7878
* @see {@link useAsyncIterState `useAsyncIterState`}
7979
*/
80-
type AsyncIterStateResult<TVal> = [IterableChannel<TVal>['iterable'], (newValue: TVal) => void];
80+
type AsyncIterStateResult<TVal> = [
81+
values: AsyncIterableSubject<TVal>,
82+
setValue: (newValue: TVal) => void,
83+
];

0 commit comments

Comments
 (0)