Skip to content

Commit 6cd3695

Browse files
authored
docs: add JSDocs around the <Iterate> component (#9)
1 parent 01d2f46 commit 6cd3695

File tree

1 file changed

+140
-5
lines changed

1 file changed

+140
-5
lines changed

src/Iterate/index.tsx

Lines changed: 140 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,105 @@ import { useAsyncIter, type IterationResult } from '../useAsyncIter/index.js';
33

44
export { Iterate, type IterateProps };
55

6+
/**
7+
* The `<Iterate>` helper component is used to format and render an async iterable (or a plain non-iterable value)
8+
* directly onto a piece of UI.
9+
*
10+
* Essentially wraps a single {@link useAsyncIter `useAsyncIter`} hook call into a component
11+
* conveniently.
12+
*
13+
* _Illustration:_
14+
*
15+
* ```tsx
16+
* import { Iterate } from 'react-async-iterators';
17+
*
18+
* function SelfUpdatingTodoList(props) {
19+
* return (
20+
* <div>
21+
* <h2>My TODOs</h2>
22+
*
23+
* <div>
24+
* Last TODO was completed at: <Iterate>{props.lastCompletedTodoDate}</Iterate>
25+
* </div>
26+
*
27+
* <ul>
28+
* <Iterate value={props.todosAsyncIter}>
29+
* {({ value: todos }) =>
30+
* todos?.map(todo =>
31+
* <li key={todo.id}>{todo.text}</li>
32+
* )
33+
* }
34+
* </Iterate>
35+
* </ul>
36+
* </div>
37+
* );
38+
* }
39+
* ```
40+
*
41+
* `<Iterate>` may be preferable over {@link useAsyncIter `useAsyncIter`} typically as the UI area it
42+
* controls the rendering for is constrainable down to the essential, saving some React elements from
43+
* unnecessary re-renderings while placable clearly and elegantly within some larger component's UI
44+
* output. In regard to {@link useAsyncIter `useAsyncIter`} being a hook though, it has to
45+
* re-render the entire component output for every new value.
46+
*
47+
* Given an async iterable as the `value` prop, this component will iterate it and render each new
48+
* value that becomes available together with any possible completion or error it may run into.
49+
* If `value` is a plain (non async iterable) value, it will simply be rendered over as-is.
50+
*
51+
* Whenever given `value` is changed from the previous one seen, `<Iterate>` will close the previous
52+
* if it was async iterable before proceeding to iterate the new `value`. Care should be taken to
53+
* avoid passing a constantly recreated iterable object across re-renders, e.g; by declaring it outside the component body or control __when__ it
54+
* should be recreated with React's [`useMemo`](https://react.dev/reference/react/useMemo).
55+
* `<Iterate>` will automatically close its iterated iterable as soon as it gets unmounted.
56+
*
57+
* @template TVal The type of values yielded by the passed iterable or otherwise type of the passed plain value itself.
58+
* @template TInitialVal The type of the initial value, defaults to `undefined`.
59+
*
60+
* @param props Props for `<Iterate>`. See {@link IterateProps `IterateProps`}.
61+
*
62+
* @returns A renderable output that's re-rendered as consequent values become available and
63+
* formatted by the function passed as `children` (or otherwise the resolved values as-are).
64+
*
65+
* @see {@link IterationResult}
66+
*
67+
* @example
68+
* ```tsx
69+
* // With the `initialValue` prop and showing usage of all properties of the iteration object
70+
* // within the child render function:
71+
*
72+
* import { Iterate } from 'react-async-iterators';
73+
*
74+
* function SelfUpdatingTodoList(props) {
75+
* return (
76+
* <div>
77+
* <h2>My TODOs</h2>
78+
*
79+
* <Iterate initialValue={[]} value={props.todosAsyncIter}>
80+
* {todosNext =>
81+
* todosNext.pendingFirst ? (
82+
* <div>Loading first todos...</div>
83+
* ) : (
84+
* <>
85+
* {todosNext.error ? (
86+
* <div>An error was encountered: {todosNext.error.toString()}</div>
87+
* ) : (
88+
* todosNext.done && <div>No additional updates for todos are expected</div>
89+
* )}
90+
*
91+
* <ul>
92+
* {todosNext.map(todo => (
93+
* <li key={todo.id}>{todo.text}</li>
94+
* ))}
95+
* </ul>
96+
* </>
97+
* )
98+
* }
99+
* </Iterate>
100+
* </div>
101+
* );
102+
* }
103+
* ```
104+
*/
6105
function Iterate<TVal, TInitialVal = undefined>(props: IterateProps<TVal, TInitialVal>): ReactNode {
7106
const renderOutput =
8107
typeof props.children === 'function'
@@ -12,26 +111,62 @@ function Iterate<TVal, TInitialVal = undefined>(props: IterateProps<TVal, TIniti
12111
return propsBetterTyped.children(next);
13112
})()
14113
: (() => {
15-
const propsBetterTyped = props as IteratePropsWithIterableAsChildren;
114+
const propsBetterTyped = props as IteratePropsWithNoRenderFunction;
16115
const next = useAsyncIter(propsBetterTyped.children, propsBetterTyped.initialValue);
17116
return next.value;
18117
})();
19118

20119
return renderOutput;
21120
}
22121

122+
/**
123+
* Props for the {@link Iterate `<Iterate>`} component.
124+
* The component accepts its props in two variants:
125+
*
126+
* 1. Providing a render function as `children` to dynamically format each state of the iteration.
127+
* 2. Providing an async iterable as `children` to render the values of the async iterable (or plain value) directly as are.
128+
*
129+
* @template TVal The type of values yielded by the passed iterable or otherwise type of the passed plain value itself.
130+
* @template TInitialVal The type of the initial value, defaults to `undefined`.
131+
*/
23132
type IterateProps<TVal, TInitialVal = undefined> =
24133
| IteratePropsWithRenderFunction<TVal, TInitialVal>
25-
| IteratePropsWithIterableAsChildren;
134+
| IteratePropsWithNoRenderFunction;
26135

27136
type IteratePropsWithRenderFunction<TVal, TInitialVal = undefined> = {
28-
initialValue?: TInitialVal;
137+
/**
138+
* The source value to iterate over, an async iterable or a plain (non async iterable) value.
139+
*/
29140
value: TVal;
141+
/**
142+
* An optional initial value, defaults to `undefined`.
143+
*/
144+
initialValue?: TInitialVal;
145+
/**
146+
* A render function that is called for each iteration state and returns something to render
147+
* out of it.
148+
*
149+
* @param nextIterationState - The current state of the iteration, including the yielded value, whether iteration is complete, any associated error, etc. (see {@link IterationResult `IterationResult`})
150+
* @returns The content to render for the current iteration state.
151+
*
152+
* @see {@link IterateProps `IterateProps`}
153+
* @see {@link IterationResult `IterationResult`}
154+
*/
30155
children: (nextIterationState: IterationResult<TVal, TInitialVal>) => ReactNode;
31156
};
32157

33-
type IteratePropsWithIterableAsChildren = {
34-
initialValue?: ReactNode;
158+
type IteratePropsWithNoRenderFunction = {
159+
/**
160+
* The `value` prop source value should not be provided for this variant since it is already
161+
* passed via `children` (see {@link IterateProps `IterateProps`}).
162+
*/
35163
value?: undefined;
164+
/**
165+
* An optional initial value, defaults to `undefined`.
166+
*/
167+
initialValue?: ReactNode;
168+
/**
169+
* The source value to render from, either an async iterable to iterate over of a plain value.
170+
*/
36171
children: ReactNode | AsyncIterable<ReactNode>;
37172
};

0 commit comments

Comments
 (0)