0
# Hook Interface
1
2
Programmatic hook interface for low-level control over Popper instances without component wrappers. The usePopper hook provides direct access to Popper.js functionality with React integration, ideal for custom implementations and advanced use cases.
3
4
## Capabilities
5
6
### usePopper Hook
7
8
Provides programmatic control over Popper instances with automatic lifecycle management and state synchronization.
9
10
```typescript { .api }
11
/**
12
* Low-level hook for programmatic control over Popper instances
13
* @param referenceElement - The reference element to position relative to
14
* @param popperElement - The popper element to position
15
* @param options - Configuration options for the Popper instance
16
* @returns Object containing styles, attributes, state, and control functions
17
*/
18
function usePopper<Modifiers>(
19
referenceElement?: Element | PopperJS.VirtualElement | null,
20
popperElement?: HTMLElement | null,
21
options?: Omit<Partial<PopperJS.Options>, 'modifiers'> & {
22
createPopper?: typeof PopperJS.createPopper;
23
modifiers?: ReadonlyArray<Modifier<Modifiers>>;
24
}
25
): UsePopperResult;
26
27
interface UsePopperResult {
28
/** Computed styles for all positioned elements (popper, arrow, etc.) */
29
styles: { [key: string]: React.CSSProperties };
30
/** HTML attributes for elements (data-popper-placement, etc.) */
31
attributes: { [key: string]: { [key: string]: string } | undefined };
32
/** Current Popper.js state object (null if not initialized) */
33
state: PopperJS.State | null;
34
/** Function to manually update popper positioning */
35
update: PopperJS.Instance['update'] | null;
36
/** Function to force immediate positioning update */
37
forceUpdate: PopperJS.Instance['forceUpdate'] | null;
38
}
39
```
40
41
### Hook Options
42
43
The usePopper hook accepts all standard Popper.js options plus React-specific enhancements:
44
45
```typescript { .api }
46
interface UsePopperOptions {
47
/** Preferred placement for the popper element */
48
placement?: PopperJS.Placement;
49
/** Positioning strategy - 'absolute' or 'fixed' */
50
strategy?: PopperJS.PositioningStrategy;
51
/** Array of Popper.js modifiers */
52
modifiers?: ReadonlyArray<Modifier<any>>;
53
/** Callback fired after first positioning update */
54
onFirstUpdate?: (state: Partial<PopperJS.State>) => void;
55
/** Custom createPopper function (for custom Popper builds) */
56
createPopper?: typeof PopperJS.createPopper;
57
}
58
```
59
60
**Usage Examples:**
61
62
```tsx
63
import React from "react";
64
import { usePopper } from "react-popper";
65
66
// Basic hook usage
67
function BasicHookExample() {
68
const [referenceElement, setReferenceElement] = React.useState(null);
69
const [popperElement, setPopperElement] = React.useState(null);
70
const [arrowElement, setArrowElement] = React.useState(null);
71
72
const { styles, attributes } = usePopper(referenceElement, popperElement, {
73
placement: "top",
74
modifiers: [
75
{ name: "arrow", options: { element: arrowElement } },
76
{ name: "offset", options: { offset: [0, 8] } },
77
],
78
});
79
80
return (
81
<>
82
<button ref={setReferenceElement}>Reference Button</button>
83
<div
84
ref={setPopperElement}
85
style={styles.popper}
86
{...attributes.popper}
87
>
88
Popper content
89
<div ref={setArrowElement} style={styles.arrow} />
90
</div>
91
</>
92
);
93
}
94
95
// Advanced hook with state management
96
function AdvancedHookExample() {
97
const [referenceElement, setReferenceElement] = React.useState(null);
98
const [popperElement, setPopperElement] = React.useState(null);
99
const [visible, setVisible] = React.useState(false);
100
101
const { styles, attributes, state, update, forceUpdate } = usePopper(
102
referenceElement,
103
popperElement,
104
{
105
placement: "bottom-start",
106
strategy: "fixed",
107
modifiers: [
108
{
109
name: "preventOverflow",
110
options: {
111
boundary: "viewport",
112
padding: 8,
113
},
114
},
115
{
116
name: "flip",
117
options: {
118
fallbackPlacements: ["top-start", "bottom-end", "top-end"],
119
},
120
},
121
],
122
onFirstUpdate: (state) => {
123
console.log("Initial popper state:", state);
124
},
125
}
126
);
127
128
// Manually update positioning when needed
129
const handleUpdate = async () => {
130
if (update) {
131
const newState = await update();
132
console.log("Updated state:", newState);
133
}
134
};
135
136
// Force update immediately
137
const handleForceUpdate = () => {
138
if (forceUpdate) {
139
const newState = forceUpdate();
140
console.log("Force updated state:", newState);
141
}
142
};
143
144
return (
145
<div>
146
<button
147
ref={setReferenceElement}
148
onClick={() => setVisible(!visible)}
149
>
150
Toggle Popper
151
</button>
152
153
{visible && (
154
<div
155
ref={setPopperElement}
156
style={{
157
...styles.popper,
158
background: "white",
159
border: "1px solid #ccc",
160
borderRadius: "4px",
161
padding: "12px",
162
boxShadow: "0 2px 8px rgba(0,0,0,0.1)",
163
zIndex: 1000,
164
}}
165
{...attributes.popper}
166
>
167
<div>Advanced Popper Content</div>
168
{state && (
169
<div style={{ fontSize: "12px", color: "#666", marginTop: "8px" }}>
170
<div>Placement: {state.placement}</div>
171
<div>Strategy: {state.options.strategy}</div>
172
</div>
173
)}
174
<div style={{ marginTop: "8px" }}>
175
<button onClick={handleUpdate}>Update</button>
176
<button onClick={handleForceUpdate}>Force Update</button>
177
</div>
178
</div>
179
)}
180
</div>
181
);
182
}
183
184
// Custom createPopper function
185
function CustomPopperExample() {
186
const [referenceElement, setReferenceElement] = React.useState(null);
187
const [popperElement, setPopperElement] = React.useState(null);
188
189
// Custom Popper configuration
190
const customCreatePopper = React.useCallback(
191
(reference, popper, options) => {
192
// Add custom logic or use a custom Popper build
193
return PopperJS.createPopper(reference, popper, {
194
...options,
195
// Add default modifiers or override behavior
196
modifiers: [
197
...options.modifiers,
198
{
199
name: "customModifier",
200
enabled: true,
201
phase: "beforeWrite",
202
fn: ({ state }) => {
203
// Custom positioning logic
204
console.log("Custom modifier executed:", state);
205
},
206
},
207
],
208
});
209
},
210
[]
211
);
212
213
const { styles, attributes } = usePopper(referenceElement, popperElement, {
214
createPopper: customCreatePopper,
215
placement: "top",
216
});
217
218
return (
219
<>
220
<button ref={setReferenceElement}>Custom Popper</button>
221
<div
222
ref={setPopperElement}
223
style={styles.popper}
224
{...attributes.popper}
225
>
226
Custom Popper Content
227
</div>
228
</>
229
);
230
}
231
```
232
233
### Virtual Elements
234
235
The usePopper hook supports virtual elements for positioning relative to coordinates rather than DOM elements:
236
237
```typescript { .api }
238
interface VirtualElement {
239
getBoundingClientRect(): DOMRect;
240
contextElement?: Element;
241
}
242
```
243
244
**Virtual Element Example:**
245
246
```tsx
247
function VirtualElementExample() {
248
const [popperElement, setPopperElement] = React.useState(null);
249
250
// Create virtual element at mouse position
251
const virtualElement = React.useMemo(() => ({
252
getBoundingClientRect: () => ({
253
width: 0,
254
height: 0,
255
top: 100,
256
right: 100,
257
bottom: 100,
258
left: 100,
259
x: 100,
260
y: 100,
261
toJSON: () => ({}),
262
}),
263
}), []);
264
265
const { styles, attributes } = usePopper(virtualElement, popperElement, {
266
placement: "bottom",
267
});
268
269
return (
270
<div
271
ref={setPopperElement}
272
style={styles.popper}
273
{...attributes.popper}
274
>
275
Positioned at coordinates (100, 100)
276
</div>
277
);
278
}
279
```
280
281
## Lifecycle Management
282
283
The usePopper hook automatically manages Popper instance lifecycle:
284
285
1. **Creation**: Creates Popper instance when both elements are available
286
2. **Updates**: Updates configuration when options change
287
3. **Cleanup**: Destroys instance when elements are removed or component unmounts
288
289
## Performance Considerations
290
291
- **Memoization**: Options are memoized to prevent unnecessary recreations
292
- **Lazy initialization**: Popper instance is only created when both elements exist
293
- **Efficient updates**: Uses React's batching for optimal re-rendering
294
295
## Error Handling
296
297
The hook handles various edge cases gracefully:
298
299
- **Null elements**: Safe handling when reference or popper elements are null
300
- **Options changes**: Smooth updates when configuration changes
301
- **Cleanup**: Proper cleanup prevents memory leaks
302
303
## Best Practices
304
305
1. **Memoize complex options:**
306
```tsx
307
const options = React.useMemo(() => ({
308
placement: "top",
309
modifiers: [{ name: "offset", options: { offset: [0, 8] } }],
310
}), []);
311
```
312
313
2. **Handle loading states:**
314
```tsx
315
const { styles, attributes, state } = usePopper(ref1, ref2, options);
316
317
if (!state) {
318
return <div>Loading...</div>;
319
}
320
```
321
322
3. **Use consistent element references:**
323
```tsx
324
// ✅ Stable references
325
const [refElement, setRefElement] = useState(null);
326
const [popperElement, setPopperElement] = useState(null);
327
328
// ❌ Avoid creating new references on each render
329
const refElement = useRef(null).current;
330
```
331
332
4. **Combine with other hooks for complex behavior:**
333
```tsx
334
function useTooltip() {
335
const [referenceElement, setReferenceElement] = useState(null);
336
const [popperElement, setPopperElement] = useState(null);
337
const [visible, setVisible] = useState(false);
338
339
const popper = usePopper(referenceElement, popperElement, {
340
placement: "top",
341
});
342
343
return {
344
...popper,
345
visible,
346
setVisible,
347
setReferenceElement,
348
setPopperElement,
349
};
350
}
351
```