- Spec files
npm-react
Describes: pkg:npm/react@18.3.x
- Description
- React is a JavaScript library for building user interfaces with declarative, component-based architecture.
- Author
- tessl
- Last updated
children.md docs/
1# Children Utilities23React 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.45## Capabilities67### Children.map89Transforms each child element using a function, similar to Array.map.1011```javascript { .api }12/**13* Maps over children elements with transformation function14* @param children - Children to iterate over15* @param fn - Function to transform each child16* @returns Array of transformed children17*/18map<T, C>(19children: C | ReadonlyArray<C>,20fn: (child: C, index: number) => T21): C extends null | undefined ? C : Array<Exclude<T, boolean | null | undefined>>;22```2324**Usage Examples:**2526```javascript27import React, { Children } from 'react';2829// Basic mapping over children30function ButtonGroup({ children, variant = 'primary' }) {31return (32<div className="button-group">33{Children.map(children, (child, index) => {34if (React.isValidElement(child)) {35return React.cloneElement(child, {36key: index,37className: `btn btn-${variant} ${child.props.className || ''}`,38'data-index': index39});40}41return child;42})}43</div>44);45}4647// Usage48<ButtonGroup variant="secondary">49<button onClick={handleSave}>Save</button>50<button onClick={handleCancel}>Cancel</button>51<button onClick={handleReset}>Reset</button>52</ButtonGroup>5354// Adding wrapper elements55function CardList({ children }) {56return (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}6667// Filtering and transforming68function ValidatedForm({ children }) {69return (70<form>71{Children.map(children, (child, index) => {72if (React.isValidElement(child) && child.type === 'input') {73return React.cloneElement(child, {74key: index,75required: true,76'aria-describedby': `${child.props.name}-error`,77className: `form-input ${child.props.className || ''}`78});79}80return child;81})}82</form>83);84}8586// Complex transformation with context87function TabContainer({ children, activeTab }) {88return (89<div className="tab-container">90{Children.map(children, (child, index) => {91if (React.isValidElement(child) && child.type.displayName === 'Tab') {92return React.cloneElement(child, {93key: index,94isActive: index === activeTab,95tabIndex: index96});97}98return child;99})}100</div>101);102}103```104105### Children.forEach106107Iterates over children without returning a new array, useful for side effects.108109```javascript { .api }110/**111* Iterates over children for side effects112* @param children - Children to iterate over113* @param fn - Function to call for each child114*/115forEach<C>(children: C | ReadonlyArray<C>, fn: (child: C, index: number) => void): void;116```117118**Usage Examples:**119120```javascript121import React, { Children, useEffect } from 'react';122123// Validation during render124function FormValidator({ children, onValidationChange }) {125useEffect(() => {126const errors = [];127128Children.forEach(children, (child, index) => {129if (React.isValidElement(child)) {130// Validate each form field131if (child.props.required && !child.props.value) {132errors.push(`Field ${index + 1} is required`);133}134135if (child.props.type === 'email' && child.props.value) {136const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;137if (!emailRegex.test(child.props.value)) {138errors.push(`Field ${index + 1} must be a valid email`);139}140}141}142});143144onValidationChange(errors);145}, [children, onValidationChange]);146147return <div>{children}</div>;148}149150// Collecting component information151function ComponentAnalyzer({ children }) {152useEffect(() => {153const componentTypes = new Set();154let totalComponents = 0;155156Children.forEach(children, (child) => {157if (React.isValidElement(child)) {158componentTypes.add(child.type.name || child.type);159totalComponents++;160}161});162163console.log('Component analysis:', {164totalComponents,165uniqueTypes: Array.from(componentTypes)166});167}, [children]);168169return <div>{children}</div>;170}171172// Event handler registration173function EventTracker({ children, onChildInteraction }) {174useEffect(() => {175Children.forEach(children, (child, index) => {176if (React.isValidElement(child) && child.props.onClick) {177// Wrap existing onClick handlers178const originalOnClick = child.props.onClick;179180console.log(`Tracking clicks for child ${index}`);181182// In real implementation, you'd clone the element with wrapped handler183onChildInteraction?.({184childIndex: index,185childType: child.type,186hasClickHandler: true187});188}189});190}, [children, onChildInteraction]);191192return <div>{children}</div>;193}194```195196### Children.count197198Returns the total number of child elements.199200```javascript { .api }201/**202* Counts the number of children203* @param children - Children to count204* @returns Number of children205*/206count(children: any): number;207```208209**Usage Examples:**210211```javascript212import React, { Children } from 'react';213214// Conditional rendering based on child count215function FlexContainer({ children, minItems = 1 }) {216const childCount = Children.count(children);217218if (childCount < minItems) {219return (220<div className="insufficient-items">221<p>Need at least {minItems} items. Currently have {childCount}.</p>222{children}223</div>224);225}226227return (228<div className={`flex-container items-${childCount}`}>229{children}230</div>231);232}233234// Dynamic grid layout235function ResponsiveGrid({ children }) {236const count = Children.count(children);237238const getGridColumns = () => {239if (count <= 2) return 'grid-cols-1';240if (count <= 4) return 'grid-cols-2';241if (count <= 6) return 'grid-cols-3';242return 'grid-cols-4';243};244245return (246<div className={`grid ${getGridColumns()} gap-4`}>247{children}248</div>249);250}251252// Usage253<ResponsiveGrid>254<Card>Item 1</Card>255<Card>Item 2</Card>256<Card>Item 3</Card>257</ResponsiveGrid>258259// Navigation with breadcrumbs260function Breadcrumbs({ children, separator = '>' }) {261const count = Children.count(children);262263return (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}280281// Pagination based on child count282function PaginatedList({ children, itemsPerPage = 10 }) {283const [currentPage, setCurrentPage] = useState(1);284const totalItems = Children.count(children);285const totalPages = Math.ceil(totalItems / itemsPerPage);286287const startIndex = (currentPage - 1) * itemsPerPage;288const endIndex = startIndex + itemsPerPage;289290const currentItems = Children.toArray(children).slice(startIndex, endIndex);291292return (293<div>294<div className="items">295{currentItems}296</div>297298<div className="pagination">299<p>300Showing {startIndex + 1}-{Math.min(endIndex, totalItems)} of {totalItems} items301</p>302303<button304disabled={currentPage === 1}305onClick={() => setCurrentPage(prev => prev - 1)}306>307Previous308</button>309310<span>Page {currentPage} of {totalPages}</span>311312<button313disabled={currentPage === totalPages}314onClick={() => setCurrentPage(prev => prev + 1)}315>316Next317</button>318</div>319</div>320);321}322```323324### Children.only325326Ensures that children contains exactly one child element and returns it.327328```javascript { .api }329/**330* Validates that children contains exactly one child331* @param children - Children to validate332* @returns Single child element333* @throws Error if children is not a single element334*/335only<C>(children: C): C extends any[] ? never : C;336```337338**Usage Examples:**339340```javascript341import React, { Children } from 'react';342343// Modal wrapper that requires single child344function Modal({ children, isOpen, onClose }) {345const child = Children.only(children); // Throws if not exactly one child346347if (!isOpen) return null;348349return (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}358359// Usage - Valid360<Modal isOpen={isModalOpen} onClose={closeModal}>361<UserProfile user={selectedUser} />362</Modal>363364// Usage - Invalid (throws error)365<Modal isOpen={isModalOpen} onClose={closeModal}>366<UserProfile user={selectedUser} />367<UserActions user={selectedUser} />368</Modal>369370// Tooltip wrapper371function Tooltip({ children, text, position = 'top' }) {372const child = Children.only(children);373const [showTooltip, setShowTooltip] = useState(false);374375return (376<div className="tooltip-wrapper">377{React.cloneElement(child, {378onMouseEnter: () => setShowTooltip(true),379onMouseLeave: () => setShowTooltip(false),380'aria-describedby': showTooltip ? 'tooltip' : undefined381})}382383{showTooltip && (384<div id="tooltip" className={`tooltip tooltip-${position}`} role="tooltip">385{text}386</div>387)}388</div>389);390}391392// Usage393<Tooltip text="Click to save your changes" position="bottom">394<button onClick={handleSave}>Save</button>395</Tooltip>396397// Form field wrapper398function FormField({ children, label, error, required }) {399const child = Children.only(children);400const id = child.props.id || `field-${Math.random().toString(36).substr(2, 9)}`;401402return (403<div className={`form-field ${error ? 'error' : ''}`}>404<label htmlFor={id} className="form-label">405{label}406{required && <span className="required">*</span>}407</label>408409{React.cloneElement(child, {410id,411'aria-invalid': !!error,412'aria-describedby': error ? `${id}-error` : undefined,413className: `form-input ${child.props.className || ''}`414})}415416{error && (417<div id={`${id}-error`} className="error-message" role="alert">418{error}419</div>420)}421</div>422);423}424425// Usage426<FormField label="Email" error={emailError} required>427<input type="email" placeholder="Enter your email" />428</FormField>429430// Error boundary for single child431function SafeWrapper({ children, fallback }) {432const child = Children.only(children);433434return (435<ErrorBoundary fallback={fallback}>436{child}437</ErrorBoundary>438);439}440```441442### Children.toArray443444Converts children to a flat array, useful for advanced manipulation.445446```javascript { .api }447/**448* Converts children to flat array449* @param children - Children to convert450* @returns Flat array of children451*/452toArray(children: ReactNode | ReactNode[]): Array<Exclude<ReactNode, boolean | null | undefined>>;453```454455**Usage Examples:**456457```javascript458import React, { Children } from 'react';459460// Reordering children461function ReorderableList({ children, order }) {462const childArray = Children.toArray(children);463464if (order && order.length === childArray.length) {465const reorderedChildren = order.map(index => childArray[index]);466return <div className="reorderable-list">{reorderedChildren}</div>;467}468469return <div className="reorderable-list">{children}</div>;470}471472// Usage473<ReorderableList order={[2, 0, 1]}>474<div>First item</div>475<div>Second item</div>476<div>Third item</div>477</ReorderableList>478479// Filtering children by type480function TypeFilter({ children, allowedTypes = [] }) {481const childArray = Children.toArray(children);482483const filteredChildren = childArray.filter(child => {484if (React.isValidElement(child)) {485return allowedTypes.includes(child.type);486}487return allowedTypes.includes('text');488});489490return <div>{filteredChildren}</div>;491}492493// Usage - only allow buttons and inputs494<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>500501// Grouping children502function GroupedLayout({ children, groupSize = 3 }) {503const childArray = Children.toArray(children);504const groups = [];505506for (let i = 0; i < childArray.length; i += groupSize) {507groups.push(childArray.slice(i, i + groupSize));508}509510return (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}520521// Usage522<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>529530// Advanced manipulation - inserting separators531function SeparatedList({ children, separator = <hr /> }) {532const childArray = Children.toArray(children);533const result = [];534535childArray.forEach((child, index) => {536result.push(child);537538// Add separator between items (not after last item)539if (index < childArray.length - 1) {540result.push(React.cloneElement(separator, { key: `sep-${index}` }));541}542});543544return <div className="separated-list">{result}</div>;545}546547// Conditional children rendering548function ConditionalChildren({ children, condition, fallback }) {549const childArray = Children.toArray(children);550551if (!condition) {552return fallback || null;553}554555// Filter out null/undefined children556const validChildren = childArray.filter(child =>557child !== null && child !== undefined && child !== false558);559560return <div>{validChildren}</div>;561}562563// Recursive flattening for nested structures564function FlattenChildren({ children }) {565const flattenRecursive = (children) => {566return Children.toArray(children).reduce((acc, child) => {567if (React.isValidElement(child) && child.props.children) {568return [...acc, child, ...flattenRecursive(child.props.children)];569}570return [...acc, child];571}, []);572};573574const flattenedChildren = flattenRecursive(children);575576return (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```587588## Types589590```javascript { .api }591// Children utility types592interface ReactChildren {593map<T, C>(594children: C | ReadonlyArray<C>,595fn: (child: C, index: number) => T596): C extends null | undefined ? C : Array<Exclude<T, boolean | null | undefined>>;597598forEach<C>(children: C | ReadonlyArray<C>, fn: (child: C, index: number) => void): void;599600count(children: any): number;601602only<C>(children: C): C extends any[] ? never : C;603604toArray(children: ReactNode | ReactNode[]): Array<Exclude<ReactNode, boolean | null | undefined>>;605}606607// React node types608type ReactNode = ReactChild | ReactFragment | ReactPortal | boolean | null | undefined;609type ReactChild = ReactElement | ReactText;610type ReactText = string | number;611type ReactFragment = {} | Iterable<ReactNode>;612```