@@ -3,6 +3,105 @@ import { useAsyncIter, type IterationResult } from '../useAsyncIter/index.js';
3
3
4
4
export { Iterate , type IterateProps } ;
5
5
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
+ */
6
105
function Iterate < TVal , TInitialVal = undefined > ( props : IterateProps < TVal , TInitialVal > ) : ReactNode {
7
106
const renderOutput =
8
107
typeof props . children === 'function'
@@ -12,26 +111,62 @@ function Iterate<TVal, TInitialVal = undefined>(props: IterateProps<TVal, TIniti
12
111
return propsBetterTyped . children ( next ) ;
13
112
} ) ( )
14
113
: ( ( ) => {
15
- const propsBetterTyped = props as IteratePropsWithIterableAsChildren ;
114
+ const propsBetterTyped = props as IteratePropsWithNoRenderFunction ;
16
115
const next = useAsyncIter ( propsBetterTyped . children , propsBetterTyped . initialValue ) ;
17
116
return next . value ;
18
117
} ) ( ) ;
19
118
20
119
return renderOutput ;
21
120
}
22
121
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
+ */
23
132
type IterateProps < TVal , TInitialVal = undefined > =
24
133
| IteratePropsWithRenderFunction < TVal , TInitialVal >
25
- | IteratePropsWithIterableAsChildren ;
134
+ | IteratePropsWithNoRenderFunction ;
26
135
27
136
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
+ */
29
140
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
+ */
30
155
children : ( nextIterationState : IterationResult < TVal , TInitialVal > ) => ReactNode ;
31
156
} ;
32
157
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
+ */
35
163
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
+ */
36
171
children : ReactNode | AsyncIterable < ReactNode > ;
37
172
} ;
0 commit comments