Commit f0c454b
authored
fix(slot): memoize composed ref in SlotClone to prevent infinite loop in React 19
Fixes radix-ui#3799
## Problem
In `SlotClone`, `composeRefs(forwardedRef, childrenRef)` is called inline during render, creating a new function identity on every render. In React 19, ref identity changes
trigger ref cleanup + re-attach. When a state setter is used as a ref (e.g., Tooltip's `setTrigger`), this causes an infinite loop:
1. New `composeRefs` → React detects ref identity change
2. React calls cleanup (`setTrigger(null)`) → state update → re-render
3. Re-render creates another new `composeRefs` → goto 1
4. "Maximum update depth exceeded"
## Solution
Memoize the composed ref via `React.useMemo` so the ref identity stays stable across renders. The memoization depends on `forwardedRef` and `childrenRef` — it only recomputes
when the actual refs change, not on every render.
## How this differs from radix-ui#3804
PR radix-ui#3804 switches to `useComposedRefs`, but comments there note that `useComposedRefs` itself is unstable when callers pass inline arrow functions. This approach memoizes
directly at the call site in `SlotClone`, which is the minimal change needed — it keeps `composeRefs` untouched and just stabilizes the ref identity where it matters.
## Validation
Running in production on a large enterprise React 19 app since Feb 2026. Zero recurrence of the infinite loop across Tooltip, Select, Popover, and Dialog components.1 parent 22473d1 commit f0c454b
1 file changed
Lines changed: 31 additions & 18 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
98 | 98 | | |
99 | 99 | | |
100 | 100 | | |
101 | | - | |
102 | | - | |
103 | | - | |
104 | | - | |
105 | | - | |
106 | | - | |
107 | | - | |
108 | | - | |
109 | | - | |
110 | | - | |
111 | | - | |
112 | | - | |
113 | | - | |
114 | | - | |
115 | | - | |
116 | | - | |
117 | | - | |
118 | | - | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
119 | 132 | | |
120 | 133 | | |
121 | 134 | | |
| |||
0 commit comments