Skip to content

Commit d813fa0

Browse files
authored
feat: implement the new useAsyncIterMulti hook (#28)
1 parent 6d30b5d commit d813fa0

12 files changed

+1160
-23
lines changed

spec/tests/useAsyncIterMulti.spec.ts

+738
Large diffs are not rendered by default.

spec/utils/asyncIterOf.ts

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export { asyncIterOf };
2+
3+
function asyncIterOf<const T>(...values: T[]) {
4+
return {
5+
async *[Symbol.asyncIterator]() {
6+
yield* values;
7+
},
8+
};
9+
}

src/common/ReactAsyncIterable.ts

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { identity } from './identity.js';
2+
3+
export {
4+
parseReactAsyncIterable,
5+
reactAsyncIterSpecialInfoSymbol,
6+
type ReactAsyncIterable,
7+
type ReactAsyncIterSpecialInfo,
8+
};
9+
10+
function parseReactAsyncIterable<T>(
11+
value: AsyncIterable<T> & Partial<ReactAsyncIterable<unknown, T>>
12+
): {
13+
baseIter: ReactAsyncIterSpecialInfo<unknown, T>['origSource'];
14+
formatFn: ReactAsyncIterSpecialInfo<unknown, T>['formatFn'];
15+
} {
16+
if (value[reactAsyncIterSpecialInfoSymbol]) {
17+
const { origSource, formatFn } = value[reactAsyncIterSpecialInfoSymbol];
18+
return {
19+
baseIter: origSource,
20+
formatFn: formatFn,
21+
};
22+
}
23+
return {
24+
baseIter: value,
25+
formatFn: identity<T>,
26+
};
27+
}
28+
29+
const reactAsyncIterSpecialInfoSymbol = Symbol('reactAsyncIterSpecialInfoSymbol');
30+
31+
type ReactAsyncIterable<TVal, TValFormatted> = AsyncIterable<TValFormatted, void, void> & {
32+
[reactAsyncIterSpecialInfoSymbol]: ReactAsyncIterSpecialInfo<TVal, TValFormatted>;
33+
};
34+
35+
type ReactAsyncIterSpecialInfo<TOrigVal, TFormattedVal> = {
36+
origSource: AsyncIterable<TOrigVal>;
37+
formatFn(value: TOrigVal, i: number): TFormattedVal;
38+
};
+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { useRef, type MutableRefObject } from 'react';
2+
3+
export { useRefWithInitialValue };
4+
5+
function useRefWithInitialValue<T = undefined>(initialValueFn: () => T): MutableRefObject<T> {
6+
const isRefInitializedRef = useRef<boolean>();
7+
8+
const ref = useRef<T>();
9+
10+
if (!isRefInitializedRef.current) {
11+
isRefInitializedRef.current = true;
12+
ref.current = initialValueFn();
13+
}
14+
15+
const refNonNull = ref as typeof ref & { current: T };
16+
17+
return refNonNull;
18+
}

src/common/identity.ts

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export { identity };
2+
3+
const identity = <T>(val: T) => val;

src/common/isAsyncIter.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,6 @@ import { type ExtractAsyncIterValue } from './ExtractAsyncIterValue.js';
33
export { isAsyncIter };
44

55
function isAsyncIter<T>(input: T): input is T & AsyncIterable<ExtractAsyncIterValue<T>> {
6-
return typeof (input as any)?.[Symbol.asyncIterator] === 'function';
6+
const inputAsAny = input as any;
7+
return typeof inputAsAny?.[Symbol.asyncIterator] === 'function';
78
}

src/common/reactAsyncIterSpecialInfoSymbol.ts

-8
This file was deleted.

src/index.ts

+10-2
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,26 @@
11
import { useAsyncIter, type IterationResult } from './useAsyncIter/index.js';
2+
import { useAsyncIterMulti } from './useAsyncIterMulti/index.js';
23
import { Iterate, type IterateProps } from './Iterate/index.js';
3-
import { iterateFormatted, type FixedRefFormattedIterable } from './iterateFormatted/index.js';
4+
import { iterateFormatted } from './iterateFormatted/index.js';
45
import { useAsyncIterState, type AsyncIterStateResult } from './useAsyncIterState/index.js';
56
import { type MaybeAsyncIterable } from './MaybeAsyncIterable/index.js';
7+
import { type ReactAsyncIterable } from './common/ReactAsyncIterable.js';
68

79
export {
810
useAsyncIter,
911
type IterationResult,
12+
useAsyncIterMulti,
1013
Iterate,
1114
Iterate as It,
1215
type IterateProps,
1316
iterateFormatted,
14-
type FixedRefFormattedIterable,
1517
useAsyncIterState,
1618
type AsyncIterStateResult,
1719
type MaybeAsyncIterable,
20+
type ReactAsyncIterable,
21+
22+
/**
23+
* @deprecated use {@link ReactAsyncIterable `ReactAsyncIterable`} instead.
24+
* */
25+
type ReactAsyncIterable as FixedRefFormattedIterable,
1826
};

src/iterateFormatted/index.ts

+4-9
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
import {
22
reactAsyncIterSpecialInfoSymbol,
3+
type ReactAsyncIterable,
34
type ReactAsyncIterSpecialInfo,
4-
} from '../common/reactAsyncIterSpecialInfoSymbol.js';
5+
} from '../common/ReactAsyncIterable.js';
56
import { asyncIterSyncMap } from '../common/asyncIterSyncMap.js';
67
import { isAsyncIter } from '../common/isAsyncIter.js';
78
import { type ExtractAsyncIterValue } from '../common/ExtractAsyncIterValue.js';
89
import { type useAsyncIter } from '../useAsyncIter/index.js'; // eslint-disable-line @typescript-eslint/no-unused-vars
910
import { type Iterate } from '../Iterate/index.js'; // eslint-disable-line @typescript-eslint/no-unused-vars
1011

11-
export { iterateFormatted, type FixedRefFormattedIterable };
12+
export { iterateFormatted };
1213

1314
/**
1415
* An optional utility to format an async iterable's values inline right where its passing into
@@ -75,9 +76,7 @@ export { iterateFormatted, type FixedRefFormattedIterable };
7576
function iterateFormatted<TIn, TOut>(
7677
source: TIn,
7778
formatFn: (value: ExtractAsyncIterValue<TIn>, i: number) => TOut
78-
): TIn extends AsyncIterable<unknown>
79-
? FixedRefFormattedIterable<ExtractAsyncIterValue<TIn>, TOut>
80-
: TOut;
79+
): TIn extends AsyncIterable<unknown> ? ReactAsyncIterable<ExtractAsyncIterValue<TIn>, TOut> : TOut;
8180

8281
function iterateFormatted(
8382
source: unknown,
@@ -107,7 +106,3 @@ function iterateFormatted(
107106
},
108107
};
109108
}
110-
111-
type FixedRefFormattedIterable<TVal, TValFormatted> = AsyncIterable<TValFormatted, void, void> & {
112-
[reactAsyncIterSpecialInfoSymbol]: ReactAsyncIterSpecialInfo<TVal, TValFormatted>;
113-
};

src/useAsyncIter/index.ts

+4-3
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@ import { type ExtractAsyncIterValue } from '../common/ExtractAsyncIterValue.js';
66
import {
77
reactAsyncIterSpecialInfoSymbol,
88
type ReactAsyncIterSpecialInfo,
9-
} from '../common/reactAsyncIterSpecialInfoSymbol.js';
9+
} from '../common/ReactAsyncIterable.js';
1010
import { type Iterate } from '../Iterate/index.js'; // eslint-disable-line @typescript-eslint/no-unused-vars
1111
import { type iterateFormatted } from '../iterateFormatted/index.js'; // eslint-disable-line @typescript-eslint/no-unused-vars
1212

1313
export { useAsyncIter, type IterationResult };
1414

15-
// TODO: The initial value can be given as a function, which the internal `useState` would invoke as it's defined to do. So the typings should take into account it possibly being a function and if that's the case then to extract its return type instead of using the function type itself
15+
// TODO: The initial values should be able to be given as functions, having them called once on mount
1616

1717
/**
1818
* `useAsyncIter` hooks up a single async iterable value into your component and its lifecycle.
@@ -145,8 +145,9 @@ const useAsyncIter: {
145145
latestInputRef.current[reactAsyncIterSpecialInfoSymbol]?.origSource ?? latestInputRef.current;
146146

147147
useMemo((): void => {
148+
const prevSourceLastestVal = stateRef.current.value;
148149
stateRef.current = {
149-
value: stateRef.current.value,
150+
value: prevSourceLastestVal,
150151
pendingFirst: true,
151152
done: false,
152153
error: undefined,

0 commit comments

Comments
 (0)