npm-react

Description
React is a JavaScript library for building user interfaces with declarative, component-based architecture.
Author
tessl
Last updated

How to use

npx @tessl/cli registry install tessl/npm-react@18.3.0

children.md docs/

1
# Children Utilities
2
3
React provides utilities for working with `props.children`, allowing you to manipulate, iterate over, and validate child elements. These utilities are essential for building flexible, reusable components.
4
5
## Capabilities
6
7
### Children.map
8
9
Transforms each child element using a function, similar to Array.map.
10
11
```javascript { .api }
12
/**
13
* Maps over children elements with transformation function
14
* @param children - Children to iterate over
15
* @param fn - Function to transform each child
16
* @returns Array of transformed children
17
*/
18
map<T, C>(
19
children: C | ReadonlyArray<C>,
20
fn: (child: C, index: number) => T
21
): C extends null | undefined ? C : Array<Exclude<T, boolean | null | undefined>>;
22
```
23
24
**Usage Examples:**
25
26
```javascript
27
import React, { Children } from 'react';
28
29
// Basic mapping over children
30
function ButtonGroup({ children, variant = 'primary' }) {
31
return (
32
<div className="button-group">
33
{Children.map(children, (child, index) => {
34
if (React.isValidElement(child)) {
35
return React.cloneElement(child, {
36
key: index,
37
className: `btn btn-${variant} ${child.props.className || ''}`,
38
'data-index': index
39
});
40
}
41
return child;
42
})}
43
</div>
44
);
45
}
46
47
// Usage
48
<ButtonGroup variant="secondary">
49
<button onClick={handleSave}>Save</button>
50
<button onClick={handleCancel}>Cancel</button>
51
<button onClick={handleReset}>Reset</button>
52
</ButtonGroup>
53
54
// Adding wrapper elements
55
function CardList({ children }) {
56
return (
57
<div className="card-list">
58
{Children.map(children, (child, index) => (
59
<div key={index} className="card-wrapper">
60
{child}
61
</div>
62
))}
63
</div>
64
);
65
}
66
67
// Filtering and transforming
68
function ValidatedForm({ children }) {
69
return (
70
<form>
71
{Children.map(children, (child, index) => {
72
if (React.isValidElement(child) && child.type === 'input') {
73
return React.cloneElement(child, {
74
key: index,
75
required: true,
76
'aria-describedby': `${child.props.name}-error`,
77
className: `form-input ${child.props.className || ''}`
78
});
79
}
80
return child;
81
})}
82
</form>
83
);
84
}
85
86
// Complex transformation with context
87
function TabContainer({ children, activeTab }) {
88
return (
89
<div className="tab-container">
90
{Children.map(children, (child, index) => {
91
if (React.isValidElement(child) && child.type.displayName === 'Tab') {
92
return React.cloneElement(child, {
93
key: index,
94
isActive: index === activeTab,
95
tabIndex: index
96
});
97
}
98
return child;
99
})}
100
</div>
101
);
102
}
103
```
104
105
### Children.forEach
106
107
Iterates over children without returning a new array, useful for side effects.
108
109
```javascript { .api }
110
/**
111
* Iterates over children for side effects
112
* @param children - Children to iterate over
113
* @param fn - Function to call for each child
114
*/
115
forEach<C>(children: C | ReadonlyArray<C>, fn: (child: C, index: number) => void): void;
116
```
117
118
**Usage Examples:**
119
120
```javascript
121
import React, { Children, useEffect } from 'react';
122
123
// Validation during render
124
function FormValidator({ children, onValidationChange }) {
125
useEffect(() => {
126
const errors = [];
127
128
Children.forEach(children, (child, index) => {
129
if (React.isValidElement(child)) {
130
// Validate each form field
131
if (child.props.required && !child.props.value) {
132
errors.push(`Field ${index + 1} is required`);
133
}
134
135
if (child.props.type === 'email' && child.props.value) {
136
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
137
if (!emailRegex.test(child.props.value)) {
138
errors.push(`Field ${index + 1} must be a valid email`);
139
}
140
}
141
}
142
});
143
144
onValidationChange(errors);
145
}, [children, onValidationChange]);
146
147
return <div>{children}</div>;
148
}
149
150
// Collecting component information
151
function ComponentAnalyzer({ children }) {
152
useEffect(() => {
153
const componentTypes = new Set();
154
let totalComponents = 0;
155
156
Children.forEach(children, (child) => {
157
if (React.isValidElement(child)) {
158
componentTypes.add(child.type.name || child.type);
159
totalComponents++;
160
}
161
});
162
163
console.log('Component analysis:', {
164
totalComponents,
165
uniqueTypes: Array.from(componentTypes)
166
});
167
}, [children]);
168
169
return <div>{children}</div>;
170
}
171
172
// Event handler registration
173
function EventTracker({ children, onChildInteraction }) {
174
useEffect(() => {
175
Children.forEach(children, (child, index) => {
176
if (React.isValidElement(child) && child.props.onClick) {
177
// Wrap existing onClick handlers
178
const originalOnClick = child.props.onClick;
179
180
console.log(`Tracking clicks for child ${index}`);
181
182
// In real implementation, you'd clone the element with wrapped handler
183
onChildInteraction?.({
184
childIndex: index,
185
childType: child.type,
186
hasClickHandler: true
187
});
188
}
189
});
190
}, [children, onChildInteraction]);
191
192
return <div>{children}</div>;
193
}
194
```
195
196
### Children.count
197
198
Returns the total number of child elements.
199
200
```javascript { .api }
201
/**
202
* Counts the number of children
203
* @param children - Children to count
204
* @returns Number of children
205
*/
206
count(children: any): number;
207
```
208
209
**Usage Examples:**
210
211
```javascript
212
import React, { Children } from 'react';
213
214
// Conditional rendering based on child count
215
function FlexContainer({ children, minItems = 1 }) {
216
const childCount = Children.count(children);
217
218
if (childCount < minItems) {
219
return (
220
<div className="insufficient-items">
221
<p>Need at least {minItems} items. Currently have {childCount}.</p>
222
{children}
223
</div>
224
);
225
}
226
227
return (
228
<div className={`flex-container items-${childCount}`}>
229
{children}
230
</div>
231
);
232
}
233
234
// Dynamic grid layout
235
function ResponsiveGrid({ children }) {
236
const count = Children.count(children);
237
238
const getGridColumns = () => {
239
if (count <= 2) return 'grid-cols-1';
240
if (count <= 4) return 'grid-cols-2';
241
if (count <= 6) return 'grid-cols-3';
242
return 'grid-cols-4';
243
};
244
245
return (
246
<div className={`grid ${getGridColumns()} gap-4`}>
247
{children}
248
</div>
249
);
250
}
251
252
// Usage
253
<ResponsiveGrid>
254
<Card>Item 1</Card>
255
<Card>Item 2</Card>
256
<Card>Item 3</Card>
257
</ResponsiveGrid>
258
259
// Navigation with breadcrumbs
260
function Breadcrumbs({ children, separator = '>' }) {
261
const count = Children.count(children);
262
263
return (
264
<nav aria-label="Breadcrumb" className="breadcrumbs">
265
<ol>
266
{Children.map(children, (child, index) => (
267
<li key={index}>
268
{child}
269
{index < count - 1 && (
270
<span className="separator" aria-hidden="true">
271
{separator}
272
</span>
273
)}
274
</li>
275
))}
276
</ol>
277
</nav>
278
);
279
}
280
281
// Pagination based on child count
282
function PaginatedList({ children, itemsPerPage = 10 }) {
283
const [currentPage, setCurrentPage] = useState(1);
284
const totalItems = Children.count(children);
285
const totalPages = Math.ceil(totalItems / itemsPerPage);
286
287
const startIndex = (currentPage - 1) * itemsPerPage;
288
const endIndex = startIndex + itemsPerPage;
289
290
const currentItems = Children.toArray(children).slice(startIndex, endIndex);
291
292
return (
293
<div>
294
<div className="items">
295
{currentItems}
296
</div>
297
298
<div className="pagination">
299
<p>
300
Showing {startIndex + 1}-{Math.min(endIndex, totalItems)} of {totalItems} items
301
</p>
302
303
<button
304
disabled={currentPage === 1}
305
onClick={() => setCurrentPage(prev => prev - 1)}
306
>
307
Previous
308
</button>
309
310
<span>Page {currentPage} of {totalPages}</span>
311
312
<button
313
disabled={currentPage === totalPages}
314
onClick={() => setCurrentPage(prev => prev + 1)}
315
>
316
Next
317
</button>
318
</div>
319
</div>
320
);
321
}
322
```
323
324
### Children.only
325
326
Ensures that children contains exactly one child element and returns it.
327
328
```javascript { .api }
329
/**
330
* Validates that children contains exactly one child
331
* @param children - Children to validate
332
* @returns Single child element
333
* @throws Error if children is not a single element
334
*/
335
only<C>(children: C): C extends any[] ? never : C;
336
```
337
338
**Usage Examples:**
339
340
```javascript
341
import React, { Children } from 'react';
342
343
// Modal wrapper that requires single child
344
function Modal({ children, isOpen, onClose }) {
345
const child = Children.only(children); // Throws if not exactly one child
346
347
if (!isOpen) return null;
348
349
return (
350
<div className="modal-overlay" onClick={onClose}>
351
<div className="modal-content" onClick={(e) => e.stopPropagation()}>
352
<button className="close-button" onClick={onClose}>×</button>
353
{child}
354
</div>
355
</div>
356
);
357
}
358
359
// Usage - Valid
360
<Modal isOpen={isModalOpen} onClose={closeModal}>
361
<UserProfile user={selectedUser} />
362
</Modal>
363
364
// Usage - Invalid (throws error)
365
<Modal isOpen={isModalOpen} onClose={closeModal}>
366
<UserProfile user={selectedUser} />
367
<UserActions user={selectedUser} />
368
</Modal>
369
370
// Tooltip wrapper
371
function Tooltip({ children, text, position = 'top' }) {
372
const child = Children.only(children);
373
const [showTooltip, setShowTooltip] = useState(false);
374
375
return (
376
<div className="tooltip-wrapper">
377
{React.cloneElement(child, {
378
onMouseEnter: () => setShowTooltip(true),
379
onMouseLeave: () => setShowTooltip(false),
380
'aria-describedby': showTooltip ? 'tooltip' : undefined
381
})}
382
383
{showTooltip && (
384
<div id="tooltip" className={`tooltip tooltip-${position}`} role="tooltip">
385
{text}
386
</div>
387
)}
388
</div>
389
);
390
}
391
392
// Usage
393
<Tooltip text="Click to save your changes" position="bottom">
394
<button onClick={handleSave}>Save</button>
395
</Tooltip>
396
397
// Form field wrapper
398
function FormField({ children, label, error, required }) {
399
const child = Children.only(children);
400
const id = child.props.id || `field-${Math.random().toString(36).substr(2, 9)}`;
401
402
return (
403
<div className={`form-field ${error ? 'error' : ''}`}>
404
<label htmlFor={id} className="form-label">
405
{label}
406
{required && <span className="required">*</span>}
407
</label>
408
409
{React.cloneElement(child, {
410
id,
411
'aria-invalid': !!error,
412
'aria-describedby': error ? `${id}-error` : undefined,
413
className: `form-input ${child.props.className || ''}`
414
})}
415
416
{error && (
417
<div id={`${id}-error`} className="error-message" role="alert">
418
{error}
419
</div>
420
)}
421
</div>
422
);
423
}
424
425
// Usage
426
<FormField label="Email" error={emailError} required>
427
<input type="email" placeholder="Enter your email" />
428
</FormField>
429
430
// Error boundary for single child
431
function SafeWrapper({ children, fallback }) {
432
const child = Children.only(children);
433
434
return (
435
<ErrorBoundary fallback={fallback}>
436
{child}
437
</ErrorBoundary>
438
);
439
}
440
```
441
442
### Children.toArray
443
444
Converts children to a flat array, useful for advanced manipulation.
445
446
```javascript { .api }
447
/**
448
* Converts children to flat array
449
* @param children - Children to convert
450
* @returns Flat array of children
451
*/
452
toArray(children: ReactNode | ReactNode[]): Array<Exclude<ReactNode, boolean | null | undefined>>;
453
```
454
455
**Usage Examples:**
456
457
```javascript
458
import React, { Children } from 'react';
459
460
// Reordering children
461
function ReorderableList({ children, order }) {
462
const childArray = Children.toArray(children);
463
464
if (order && order.length === childArray.length) {
465
const reorderedChildren = order.map(index => childArray[index]);
466
return <div className="reorderable-list">{reorderedChildren}</div>;
467
}
468
469
return <div className="reorderable-list">{children}</div>;
470
}
471
472
// Usage
473
<ReorderableList order={[2, 0, 1]}>
474
<div>First item</div>
475
<div>Second item</div>
476
<div>Third item</div>
477
</ReorderableList>
478
479
// Filtering children by type
480
function TypeFilter({ children, allowedTypes = [] }) {
481
const childArray = Children.toArray(children);
482
483
const filteredChildren = childArray.filter(child => {
484
if (React.isValidElement(child)) {
485
return allowedTypes.includes(child.type);
486
}
487
return allowedTypes.includes('text');
488
});
489
490
return <div>{filteredChildren}</div>;
491
}
492
493
// Usage - only allow buttons and inputs
494
<TypeFilter allowedTypes={['button', 'input']}>
495
<button>Valid button</button>
496
<div>This will be filtered out</div>
497
<input type="text" />
498
<span>This will be filtered out</span>
499
</TypeFilter>
500
501
// Grouping children
502
function GroupedLayout({ children, groupSize = 3 }) {
503
const childArray = Children.toArray(children);
504
const groups = [];
505
506
for (let i = 0; i < childArray.length; i += groupSize) {
507
groups.push(childArray.slice(i, i + groupSize));
508
}
509
510
return (
511
<div className="grouped-layout">
512
{groups.map((group, index) => (
513
<div key={index} className="group">
514
{group}
515
</div>
516
))}
517
</div>
518
);
519
}
520
521
// Usage
522
<GroupedLayout groupSize={2}>
523
<Card>Item 1</Card>
524
<Card>Item 2</Card>
525
<Card>Item 3</Card>
526
<Card>Item 4</Card>
527
<Card>Item 5</Card>
528
</GroupedLayout>
529
530
// Advanced manipulation - inserting separators
531
function SeparatedList({ children, separator = <hr /> }) {
532
const childArray = Children.toArray(children);
533
const result = [];
534
535
childArray.forEach((child, index) => {
536
result.push(child);
537
538
// Add separator between items (not after last item)
539
if (index < childArray.length - 1) {
540
result.push(React.cloneElement(separator, { key: `sep-${index}` }));
541
}
542
});
543
544
return <div className="separated-list">{result}</div>;
545
}
546
547
// Conditional children rendering
548
function ConditionalChildren({ children, condition, fallback }) {
549
const childArray = Children.toArray(children);
550
551
if (!condition) {
552
return fallback || null;
553
}
554
555
// Filter out null/undefined children
556
const validChildren = childArray.filter(child =>
557
child !== null && child !== undefined && child !== false
558
);
559
560
return <div>{validChildren}</div>;
561
}
562
563
// Recursive flattening for nested structures
564
function FlattenChildren({ children }) {
565
const flattenRecursive = (children) => {
566
return Children.toArray(children).reduce((acc, child) => {
567
if (React.isValidElement(child) && child.props.children) {
568
return [...acc, child, ...flattenRecursive(child.props.children)];
569
}
570
return [...acc, child];
571
}, []);
572
};
573
574
const flattenedChildren = flattenRecursive(children);
575
576
return (
577
<div className="flattened">
578
{flattenedChildren.map((child, index) => (
579
<div key={index} className="flattened-item">
580
{child}
581
</div>
582
))}
583
</div>
584
);
585
}
586
```
587
588
## Types
589
590
```javascript { .api }
591
// Children utility types
592
interface ReactChildren {
593
map<T, C>(
594
children: C | ReadonlyArray<C>,
595
fn: (child: C, index: number) => T
596
): C extends null | undefined ? C : Array<Exclude<T, boolean | null | undefined>>;
597
598
forEach<C>(children: C | ReadonlyArray<C>, fn: (child: C, index: number) => void): void;
599
600
count(children: any): number;
601
602
only<C>(children: C): C extends any[] ? never : C;
603
604
toArray(children: ReactNode | ReactNode[]): Array<Exclude<ReactNode, boolean | null | undefined>>;
605
}
606
607
// React node types
608
type ReactNode = ReactChild | ReactFragment | ReactPortal | boolean | null | undefined;
609
type ReactChild = ReactElement | ReactText;
610
type ReactText = string | number;
611
type ReactFragment = {} | Iterable<ReactNode>;
612
```