Skip to content

feat(Iterate): support initial value in function form #49

New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Merged
merged 6 commits into from
Jan 9, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions spec/tests/Iterate.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -580,6 +580,50 @@ describe('`Iterate` component', () => {
}
);

it(
gray(
'When given an initial value as a function, calls it once on mount and uses its result as the initial value correctly'
),
async () => {
const channel = new IteratorChannelTestHelper<string>();
const initValFn = vi.fn(() => '_');
const renderFn = vi.fn() as Mock<
(next: IterationResult<AsyncIterable<string>, string>) => any
>;

const Component = (props: { value: AsyncIterable<string> }) => (
<Iterate value={props.value} initialValue={initValFn}>
{renderFn.mockImplementation(() => (
<div id="test-created-elem">Render count: {renderFn.mock.calls.length}</div>
))}
</Iterate>
);

const rendered = render(<></>);

await act(() => rendered.rerender(<Component value={channel} />));
const renderedHtmls = [rendered.container.innerHTML];

await act(() => rendered.rerender(<Component value={channel} />));
renderedHtmls.push(rendered.container.innerHTML);

await act(() => channel.put('a'));
renderedHtmls.push(rendered.container.innerHTML);

expect(initValFn).toHaveBeenCalledOnce();
expect(renderFn.mock.calls).toStrictEqual([
[{ value: '_', pendingFirst: true, done: false, error: undefined }],
[{ value: '_', pendingFirst: true, done: false, error: undefined }],
[{ value: 'a', pendingFirst: false, done: false, error: undefined }],
]);
expect(renderedHtmls).toStrictEqual([
'<div id="test-created-elem">Render count: 1</div>',
'<div id="test-created-elem">Render count: 2</div>',
'<div id="test-created-elem">Render count: 3</div>',
]);
}
);

it(gray('When unmounted will close the last active iterator it held'), async () => {
let lastRenderFnInput: undefined | IterationResult<string | undefined>;

Expand Down
22 changes: 14 additions & 8 deletions src/Iterate/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { type ReactNode } from 'react';
import { type MaybeFunction } from '../common/MaybeFunction.js';
import { type MaybeAsyncIterable } from '../MaybeAsyncIterable/index.js';
import { useAsyncIter, type IterationResult } from '../useAsyncIter/index.js';

export { Iterate, type IterateProps };
Expand Down Expand Up @@ -110,7 +112,7 @@ function Iterate<TVal, TInitialVal = undefined>(props: IterateProps<TVal, TIniti
const propsBetterTyped = props as IteratePropsWithRenderFunction<TVal, TInitialVal>;
const next = useAsyncIter(
propsBetterTyped.value,
propsBetterTyped.initialValue as TInitialVal
propsBetterTyped.initialValue as NonNullable<typeof propsBetterTyped.initialValue>
);
return propsBetterTyped.children(next);
})()
Expand Down Expand Up @@ -143,16 +145,18 @@ type IteratePropsWithRenderFunction<TVal, TInitialVal = undefined> = {
*/
value: TVal;
/**
* An optional initial value, defaults to `undefined`. Will be the value provided inside the child
* render function when `<Iterate>` first renders on being mounted and while it's pending its first
* value to be yielded.
* An optional starting value, defaults to `undefined`. Will be the value inserted into the child render
* function when `<Iterate>` first renders during mount and while it's pending its first value to be
* yielded.
*
* You can pass an actual value, or a function that returns a value (which `<Iterate>` will call once during mounting).
*/
initialValue?: TInitialVal;
initialValue?: MaybeFunction<TInitialVal>;
/**
* A render function that is called for each step of the iteration, returning something to render
* out of it.
*
* @param nextIterationState - The current state of the iteration, including the yielded value, whether iteration is complete, any associated error, etc. (see {@link IterationResult `IterationResult`})
* @param nextIterationState - The current state of the iteration, including the yielded value, whether iteration is complete, any associated error, etc. (see {@link IterationResult `IterationResult`}).
* @returns The content to render for the current iteration state.
*
* @see {@link IterateProps `IterateProps`}
Expand All @@ -169,10 +173,12 @@ type IteratePropsWithNoRenderFunction = {
value?: undefined;
/**
* An optional initial value, defaults to `undefined`.
*
* You can pass an actual value, or a function that returns a value (which `<Iterate>` will call once during mounting).
*/
initialValue?: ReactNode;
initialValue?: MaybeFunction<ReactNode>;
/**
* The source value to render from, either an async iterable to iterate over of a plain value.
*/
children: ReactNode | AsyncIterable<ReactNode>;
children: MaybeAsyncIterable<ReactNode>;
};
2 changes: 2 additions & 0 deletions src/IterateMulti/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { type iterateFormatted } from '../iterateFormatted/index.js'; // eslint-

export { IterateMulti, type IterateMultiProps };

// TODO: The initial values should be able to be given in function/s form, with consideration for iterable sources that could be added in dynamically.

/**
* The `<IterateMulti>` helper component (also exported as `<ItMulti>`) is used to combine and render
* any number of async iterables (or plain non-iterable values) directly onto a piece of UI.
Expand Down
2 changes: 1 addition & 1 deletion src/useAsyncIterMulti/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { iterateAsyncIterWithCallbacks } from '../common/iterateAsyncIterWithCal

export { useAsyncIterMulti, type IterationResult, type IterationResultSet };

// TODO: The initial values should be able to be given as functions, having them called once on mount if so
// TODO: The initial values should be able to be given in function/s form, with consideration for iterable sources that could be added in dynamically.

/**
* `useAsyncIterMulti` hooks up multiple async iterables to your component and its lifecycle, letting
Expand Down
Loading