Skip to content

Commit 06f97d1

Browse files
authored
fix: utils consuming iterables wrapped with iterateFormatted's populating the format function's index arg correctly to treat the iterable's current value as the first yield (#102)
1 parent 6a92373 commit 06f97d1

File tree

6 files changed

+82
-52
lines changed

6 files changed

+82
-52
lines changed

spec/tests/Iterate.spec.tsx

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

674674
expect(renderFn.mock.calls.flat()).toStrictEqual(
675-
['a_current', 'a', 'b_current_formatted_0', 'b_formatted_0'].map(value => ({
675+
['a_current', 'a', 'b_current_formatted_0', 'b_formatted_1'].map(value => ({
676676
value,
677677
pendingFirst: false,
678678
done: false,

spec/tests/IterateMulti.spec.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -917,7 +917,7 @@ describe('`IterateMulti` hook', () => {
917917
{ value: 'a', pendingFirst: false, done: false, error: undefined },
918918
],
919919
[
920-
{ value: 'b_formatted_0', pendingFirst: false, done: false, error: undefined },
920+
{ value: 'b_formatted_1', pendingFirst: false, done: false, error: undefined },
921921
{ value: 'a', pendingFirst: false, done: false, error: undefined },
922922
],
923923
]);

spec/tests/useAsyncIter.spec.ts

Lines changed: 47 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -434,7 +434,7 @@ describe('`useAsyncIter` hook', () => {
434434
gray(
435435
'When given an iterable with a `.value.current` property at any point, uses that as the current value and skips the pending stage'
436436
),
437-
() =>
437+
() => {
438438
([{ initialValue: undefined }, { initialValue: '_' }] as const).forEach(
439439
({ initialValue }) => {
440440
it(
@@ -454,28 +454,21 @@ describe('`useAsyncIter` hook', () => {
454454

455455
const results: any[] = [];
456456

457-
for (const run of [
457+
for (const next of [
458+
() => renderedHook.rerender({ value: channel1 }),
459+
() => channel1.put('a'),
458460
() =>
459-
act(() =>
460-
renderedHook.rerender({
461-
value: channel1,
462-
})
463-
),
464-
() => act(() => channel1.put('a')),
465-
() =>
466-
act(() =>
467-
renderedHook.rerender({
468-
value: iterateFormatted(channel2, (val, i) => `${val}_formatted_${i}`),
469-
})
470-
),
471-
() => act(() => channel2.put('b')),
461+
renderedHook.rerender({
462+
value: iterateFormatted(channel2, (val, i) => `${val}_formatted_${i}`),
463+
}),
464+
() => channel2.put('b'),
472465
]) {
473-
await run();
466+
await act(next);
474467
results.push(renderedHook.result.current);
475468
}
476469

477470
expect(results).toStrictEqual(
478-
['a_current', 'a', 'b_current_formatted_0', 'b_formatted_0'].map(value => ({
471+
['a_current', 'a', 'b_current_formatted_0', 'b_formatted_1'].map(value => ({
479472
value,
480473
pendingFirst: false,
481474
done: false,
@@ -485,7 +478,38 @@ describe('`useAsyncIter` hook', () => {
485478
}
486479
);
487480
}
488-
)
481+
);
482+
483+
it(gray('with a formatted iterable'), async () => {
484+
let timesRerendered = 0;
485+
const channel = Object.assign(new IteratorChannelTestHelper<string>(), {
486+
value: { current: 'a_current' },
487+
});
488+
489+
const renderedHook = await act(() =>
490+
renderHook(() => {
491+
timesRerendered++;
492+
return useAsyncIter(iterateFormatted(channel, (val, i) => `${val}_formatted_${i}`));
493+
})
494+
);
495+
expect(timesRerendered).toStrictEqual(1);
496+
expect(renderedHook.result.current).toStrictEqual({
497+
value: 'a_current_formatted_0',
498+
pendingFirst: false,
499+
done: false,
500+
error: undefined,
501+
});
502+
503+
await act(() => channel.put('a_next'));
504+
expect(timesRerendered).toStrictEqual(2);
505+
expect(renderedHook.result.current).toStrictEqual({
506+
value: 'a_next_formatted_1',
507+
pendingFirst: false,
508+
done: false,
509+
error: undefined,
510+
});
511+
});
512+
}
489513
);
490514

491515
it(gray('When unmounted will close the last active iterator it held'), async () => {
@@ -627,19 +651,19 @@ describe('`useAsyncIter` hook', () => {
627651

628652
const renderedHook = await act(() =>
629653
renderHook(
630-
({ formatInto }) => {
654+
({ formatTo }) => {
631655
timesRerendered++;
632-
return useAsyncIter(iterateFormatted(channel, _ => formatInto));
656+
return useAsyncIter(iterateFormatted(channel, _ => formatTo));
633657
},
634658
{
635-
initialProps: { formatInto: '' as string | null | undefined },
659+
initialProps: { formatTo: '' as string | null | undefined },
636660
}
637661
)
638662
);
639663

640664
await act(() => {
641665
channel.put('a');
642-
renderedHook.rerender({ formatInto: null });
666+
renderedHook.rerender({ formatTo: null });
643667
});
644668
expect(timesRerendered).toStrictEqual(3);
645669
expect(renderedHook.result.current).toStrictEqual({
@@ -651,7 +675,7 @@ describe('`useAsyncIter` hook', () => {
651675

652676
await act(() => {
653677
channel.put('b');
654-
renderedHook.rerender({ formatInto: undefined });
678+
renderedHook.rerender({ formatTo: undefined });
655679
});
656680
expect(timesRerendered).toStrictEqual(5);
657681
expect(renderedHook.result.current).toStrictEqual({

spec/tests/useAsyncIterMulti.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -632,7 +632,7 @@ describe('`useAsyncIterMulti` hook', () => {
632632
{ value: 'a', pendingFirst: false, done: false, error: undefined },
633633
],
634634
[
635-
{ value: 'b_formatted_0', pendingFirst: false, done: false, error: undefined },
635+
{ value: 'b_formatted_1', pendingFirst: false, done: false, error: undefined },
636636
{ value: 'a', pendingFirst: false, done: false, error: undefined },
637637
],
638638
]);

src/common/useAsyncItersImperatively/index.ts

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -120,22 +120,20 @@ const useAsyncItersImperatively: {
120120
return existingIterState.currState;
121121
}
122122

123-
const formattedIter: AsyncIterable<unknown> = (() => {
124-
let iterationIdx = 0;
125-
return asyncIterSyncMap(baseIter, value => iterState.formatFn(value, iterationIdx++));
126-
})();
127-
128123
const inputWithMaybeCurrentValue = input as typeof input & {
129124
value?: AsyncIterableSubject<unknown>['value'];
130125
};
131126

132-
let pendingFirst;
127+
let iterationIdx: number;
128+
let pendingFirst: boolean;
133129
let startingValue;
134130

135131
if (inputWithMaybeCurrentValue.value) {
132+
iterationIdx = 1; // If source has a current value, it should have been the "first iteration" already, so in that case the right up next one here is *the second* already (index of 1)
136133
pendingFirst = false;
137134
startingValue = inputWithMaybeCurrentValue.value.current;
138135
} else {
136+
iterationIdx = 0;
139137
pendingFirst = true;
140138
startingValue =
141139
i < ref.current.currResults.length
@@ -147,11 +145,17 @@ const useAsyncItersImperatively: {
147145
);
148146
}
149147

148+
const formattedIter: AsyncIterable<unknown> = asyncIterSyncMap(baseIter, value =>
149+
iterState.formatFn(value, iterationIdx++)
150+
);
151+
150152
const destroyFn = iterateAsyncIterWithCallbacks(formattedIter, startingValue, next => {
151153
iterState.currState = { pendingFirst: false, ...next };
152-
const newPrevResults = ref.current.currResults.slice(0); // Using `.slice(0)` in attempt to copy the array faster than `[...ref.current.currResults]` would
153-
newPrevResults[i] = iterState.currState;
154-
ref.current.currResults = newPrevResults as typeof ref.current.currResults;
154+
ref.current.currResults = (() => {
155+
const newResults = ref.current.currResults.slice(0); // Using `.slice(0)` in attempt to copy the array faster than `[...ref.current.currResults]` would
156+
newResults[i] = iterState.currState;
157+
return newResults as typeof ref.current.currResults;
158+
})();
155159
onYieldCb(ref.current.currResults);
156160
});
157161

src/useAsyncIter/index.ts

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
} from '../common/ReactAsyncIterable.js';
1212
import { iterateAsyncIterWithCallbacks } from '../common/iterateAsyncIterWithCallbacks.js';
1313
import { callOrReturn } from '../common/callOrReturn.js';
14+
import { asyncIterSyncMap } from '../common/asyncIterSyncMap.js';
1415
import { type Iterate } from '../Iterate/index.js'; // eslint-disable-line @typescript-eslint/no-unused-vars
1516
import { type iterateFormatted } from '../iterateFormatted/index.js'; // eslint-disable-line @typescript-eslint/no-unused-vars
1617

@@ -148,19 +149,19 @@ const useAsyncIter: {
148149
const iterSourceRefToUse =
149150
latestInputRef.current[reactAsyncIterSpecialInfoSymbol]?.origSource ?? latestInputRef.current;
150151

151-
useMemo((): void => {
152-
const latestInputRefCurrent = latestInputRef.current!;
152+
const latestInputRefCurrent = latestInputRef.current!;
153153

154-
let value;
154+
useMemo((): void => {
155155
let pendingFirst;
156+
let value;
156157

157158
if (latestInputRefCurrent.value) {
158-
value = latestInputRefCurrent.value.current;
159159
pendingFirst = false;
160+
value = latestInputRefCurrent.value.current;
160161
} else {
161162
const prevSourceLastestVal = stateRef.current.value;
162-
value = prevSourceLastestVal;
163163
pendingFirst = true;
164+
value = prevSourceLastestVal;
164165
}
165166

166167
stateRef.current = {
@@ -172,22 +173,23 @@ const useAsyncIter: {
172173
}, [iterSourceRefToUse]);
173174

174175
useEffect(() => {
175-
let iterationIdx = 0;
176+
const formattedIter = (() => {
177+
let iterationIdx = latestInputRefCurrent.value ? 1 : 0; // If source has a current value, it should have been the "first iteration" already, so in that case the right up next one here is *the second* already (index of 1)
176178

177-
return iterateAsyncIterWithCallbacks(iterSourceRefToUse, stateRef.current.value, next => {
178-
const possibleGivenFormatFn =
179-
latestInputRef.current?.[reactAsyncIterSpecialInfoSymbol]?.formatFn;
179+
return asyncIterSyncMap(iterSourceRefToUse, value => {
180+
const possibleGivenFormatFn =
181+
latestInputRef.current?.[reactAsyncIterSpecialInfoSymbol]?.formatFn;
180182

181-
const formattedValue = possibleGivenFormatFn
182-
? possibleGivenFormatFn(next.value, iterationIdx++)
183-
: next.value;
183+
const formattedValue = possibleGivenFormatFn
184+
? possibleGivenFormatFn(value, iterationIdx++)
185+
: value;
184186

185-
stateRef.current = {
186-
...next,
187-
pendingFirst: false,
188-
value: formattedValue,
189-
};
187+
return formattedValue;
188+
});
189+
})();
190190

191+
return iterateAsyncIterWithCallbacks(formattedIter, stateRef.current.value, next => {
192+
stateRef.current = { ...next, pendingFirst: false };
191193
rerender();
192194
});
193195
}, [iterSourceRefToUse]);

0 commit comments

Comments
 (0)