Skip to content

Commit b168efd

Browse files
authored
docs(useSlots): add file header explaining algorithm and flow
1 parent 0d88889 commit b168efd

File tree

1 file changed

+57
-12
lines changed

1 file changed

+57
-12
lines changed

packages/react/src/hooks/useSlots.ts

Lines changed: 57 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,59 @@ import {warning} from '../utils/warning'
33
import {isSlot} from '../utils/is-slot'
44
import type {SlotMarker} from '../utils/types'
55

6-
// slot config allows 2 options:
7-
// 1. Component to match, example: { leadingVisual: LeadingVisual }
6+
/**
7+
* useSlots - Extract slot components from children for SSR-compatible slot APIs.
8+
*
9+
* Given a list of children and a config mapping slot names to component types,
10+
* separates children into two groups: matched slots and the rest.
11+
*
12+
* Config: { leadingVisual: LeadingVisual, description: Description }
13+
*
14+
* Children: Matching: Output:
15+
* +----------------+ slots = {
16+
* | LeadingVisual | -> matches slot 0 --> leadingVisual: <LeadingVisual />
17+
* | "Project name" | -> no match --> description: <Description />
18+
* | Description | -> matches slot 1 --> }
19+
* | TrailingVisual | -> no match --> rest = ["Project name", <TrailingVisual />]
20+
* +----------------+
21+
*
22+
* Performance-sensitive: called per item in lists (e.g. 100-item ActionList
23+
* calls this 101 times per render). Key optimizations:
24+
*
25+
* 1. for-loop matching instead of findIndex (no closure allocation per child)
26+
* 2. Pre-computed isArrayMatcher[] (avoids Array.isArray in hot loop)
27+
* 3. Short-circuit: once all slots filled, skip matching entirely
28+
* - In production: single integer comparison, straight to rest
29+
* - In dev: scans for duplicates to warn, then to rest
30+
*
31+
* Flow per child:
32+
*
33+
* child ──> isValidElement? ──no──> rest[]
34+
* |
35+
* yes
36+
* |
37+
* v
38+
* all slots filled? ──yes──> rest[] (prod)
39+
* | |
40+
* no [dev: check for
41+
* | duplicate warning]
42+
* v
43+
* match against unfilled slots
44+
* |
45+
* found match? ──no──> rest[]
46+
* |
47+
* yes
48+
* |
49+
* v
50+
* slots[key] = child, slotsFound++
51+
*
52+
* Slot config supports two matcher styles:
53+
* 1. Component reference: { visual: LeadingVisual }
54+
* 2. Component + test fn: { block: [Description, props => props.variant === 'block'] }
55+
*/
56+
57+
// Slot config: Component reference, or [Component, testFn] tuple
858
type ComponentMatcher = React.ElementType<Props>
9-
// 2. Component to match + a test function, example: { blockDescription: [Description, props => props.variant === 'block'] }
1059
type ComponentAndPropsMatcher = [ComponentMatcher, (props: Props) => boolean]
1160

1261
export type SlotConfig = Record<string, ComponentMatcher | ComponentAndPropsMatcher>
@@ -19,21 +68,17 @@ type SlotElements<Config extends SlotConfig> = {
1968
[Property in keyof Config]: SlotValue<Config, Property>
2069
}
2170

22-
type SlotValue<Config, Property extends keyof Config> = Config[Property] extends React.ElementType // config option 1
71+
type SlotValue<Config, Property extends keyof Config> = Config[Property] extends React.ElementType
2372
? React.ReactElement<React.ComponentPropsWithoutRef<Config[Property]>, Config[Property]>
2473
: Config[Property] extends readonly [
25-
infer ElementType extends React.ElementType, // config option 2, infer array[0] as component
74+
infer ElementType extends React.ElementType,
2675
// eslint-disable-next-line @typescript-eslint/no-unused-vars
27-
infer _testFn, // even though we don't use testFn, we need to infer it to support types for slots.*.props
76+
infer _testFn,
2877
]
2978
? React.ReactElement<React.ComponentPropsWithoutRef<ElementType>, ElementType>
30-
: never // useful for narrowing types, third option is not possible
79+
: never
3180

32-
/**
33-
* Extract components from `children` so we can render them in different places,
34-
* allowing us to implement components with SSR-compatible slot APIs.
35-
* Note: We can only extract direct children, not nested ones.
36-
*/
81+
/** Extract slot components from children. See file header for details. */
3782
export function useSlots<Config extends SlotConfig>(
3883
children: React.ReactNode,
3984
config: Config,

0 commit comments

Comments
 (0)