Skip to content

Commit 6c0182f

Browse files
authored
perf(useSlots): short-circuit and skip filled slots
1 parent b69325f commit 6c0182f

File tree

1 file changed

+28
-20
lines changed

1 file changed

+28
-20
lines changed

packages/react/src/hooks/useSlots.ts

Lines changed: 28 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,15 @@ export function useSlots<Config extends SlotConfig>(
3939
config: Config,
4040
): [Partial<SlotElements<Config>>, React.ReactNode[]] {
4141
// Object mapping slot names to their elements
42-
const slots: Partial<SlotElements<Config>> = mapValues(config, () => undefined)
42+
const slots: Partial<SlotElements<Config>> = {} as Partial<SlotElements<Config>>
4343

4444
// Array of elements that are not slots
4545
const rest: React.ReactNode[] = []
4646

4747
const keys = Object.keys(config) as Array<keyof Config>
4848
const values = Object.values(config)
49+
const totalSlots = keys.length
50+
let slotsFound = 0
4951

5052
// eslint-disable-next-line github/array-foreach
5153
React.Children.forEach(children, child => {
@@ -54,44 +56,50 @@ export function useSlots<Config extends SlotConfig>(
5456
return
5557
}
5658

57-
const index = values.findIndex(value => {
59+
// Short-circuit: if all slots are filled, remaining children go to rest
60+
if (slotsFound === totalSlots) {
61+
rest.push(child)
62+
return
63+
}
64+
65+
let matchedIndex = -1
66+
for (let i = 0; i < totalSlots; i++) {
67+
// Skip already-filled slots
68+
if (slots[keys[i]] !== undefined) continue
69+
70+
const value = values[i]
5871
if (Array.isArray(value)) {
5972
const [component, testFn] = value
60-
return (child.type === component || isSlot(child, component as SlotMarker)) && testFn(child.props)
73+
if ((child.type === component || isSlot(child, component as SlotMarker)) && testFn(child.props)) {
74+
matchedIndex = i
75+
break
76+
}
6177
} else {
62-
return child.type === value || isSlot(child, value as SlotMarker)
78+
if (child.type === value || isSlot(child, value as SlotMarker)) {
79+
matchedIndex = i
80+
break
81+
}
6382
}
64-
})
83+
}
6584

6685
// If the child is not a slot, add it to the `rest` array
67-
if (index === -1) {
86+
if (matchedIndex === -1) {
6887
rest.push(child)
6988
return
7089
}
7190

72-
const slotKey = keys[index]
91+
const slotKey = keys[matchedIndex]
7392

7493
// If slot is already filled, ignore duplicates
75-
if (slots[slotKey]) {
94+
if (slots[slotKey] !== undefined) {
7695
warning(true, `Found duplicate "${String(slotKey)}" slot. Only the first will be rendered.`)
7796
return
7897
}
7998

8099
// If the child is a slot, add it to the `slots` object
81-
82100
slots[slotKey] = child as SlotValue<Config, keyof Config>
101+
slotsFound++
83102
})
84103

85104
return [slots, rest]
86105
}
87-
88-
/** Map the values of an object */
89-
function mapValues<T extends Record<string, unknown>, V>(obj: T, fn: (value: T[keyof T]) => V) {
90-
return Object.keys(obj).reduce(
91-
(result, key: keyof T) => {
92-
result[key] = fn(obj[key])
93-
return result
94-
},
95-
{} as Record<keyof T, V>,
96-
)
97-
}

0 commit comments

Comments
 (0)