0
# Observers & Detection
1
2
Observer-based hooks for intersection detection, resize monitoring, mutation tracking, and viewport visibility using modern browser Observer APIs.
3
4
## Capabilities
5
6
### useIntersection
7
8
Intersection Observer API integration for detecting when elements enter/leave the viewport.
9
10
```typescript { .api }
11
/**
12
* Intersection Observer API integration
13
* @param options - IntersectionObserver configuration options
14
* @returns Object with ref callback and intersection entry
15
*/
16
function useIntersection<T extends HTMLElement = any>(
17
options?: IntersectionObserverInit
18
): UseIntersectionReturnValue<T>;
19
20
interface UseIntersectionReturnValue<T> {
21
ref: React.RefCallback<T | null>;
22
entry: IntersectionObserverEntry | null;
23
}
24
```
25
26
**Usage Examples:**
27
28
```typescript
29
import { useIntersection } from "@mantine/hooks";
30
31
// Basic intersection detection
32
function LazyImage({ src, alt }: { src: string; alt: string }) {
33
const { ref, entry } = useIntersection({
34
threshold: 0.1,
35
});
36
37
const isVisible = entry?.isIntersecting;
38
39
return (
40
<div ref={ref}>
41
{isVisible ? (
42
<img src={src} alt={alt} />
43
) : (
44
<div style={{ height: '200px', background: '#f0f0f0' }}>
45
Loading...
46
</div>
47
)}
48
</div>
49
);
50
}
51
52
// Infinite scrolling
53
function InfiniteList({ items, onLoadMore }: Props) {
54
const { ref, entry } = useIntersection({
55
threshold: 1.0,
56
});
57
58
useEffect(() => {
59
if (entry?.isIntersecting) {
60
onLoadMore();
61
}
62
}, [entry?.isIntersecting, onLoadMore]);
63
64
return (
65
<div>
66
{items.map(item => <Item key={item.id} {...item} />)}
67
<div ref={ref}>Loading more...</div>
68
</div>
69
);
70
}
71
```
72
73
### useResizeObserver
74
75
Resize Observer API for monitoring element size changes.
76
77
```typescript { .api }
78
/**
79
* Resize Observer API for element size monitoring
80
* @param callback - Function called when element resizes
81
* @returns Ref callback to attach to target element
82
*/
83
function useResizeObserver<T extends HTMLElement = any>(
84
callback: (entries: ResizeObserverEntry[], observer: ResizeObserver) => void
85
): React.RefCallback<T | null>;
86
```
87
88
**Usage Examples:**
89
90
```typescript
91
import { useResizeObserver } from "@mantine/hooks";
92
93
function ResponsiveComponent() {
94
const [size, setSize] = useState({ width: 0, height: 0 });
95
96
const ref = useResizeObserver<HTMLDivElement>((entries) => {
97
const entry = entries[0];
98
if (entry) {
99
setSize({
100
width: entry.contentRect.width,
101
height: entry.contentRect.height,
102
});
103
}
104
});
105
106
return (
107
<div ref={ref} style={{ resize: 'both', overflow: 'auto', border: '1px solid' }}>
108
<p>Size: {size.width} × {size.height}</p>
109
<p>Resize me!</p>
110
</div>
111
);
112
}
113
```
114
115
### useElementSize
116
117
Convenience hook for tracking element dimensions.
118
119
```typescript { .api }
120
/**
121
* Track element dimensions
122
* @returns Object with ref, width, and height
123
*/
124
function useElementSize<T extends HTMLElement = any>(): {
125
ref: React.RefCallback<T | null>;
126
width: number;
127
height: number;
128
};
129
```
130
131
**Usage Examples:**
132
133
```typescript
134
import { useElementSize } from "@mantine/hooks";
135
136
function SizeAwareComponent() {
137
const { ref, width, height } = useElementSize();
138
139
return (
140
<div ref={ref}>
141
<p>Element size: {width} × {height}</p>
142
{width < 300 && <div>Mobile layout</div>}
143
{width >= 300 && width < 768 && <div>Tablet layout</div>}
144
{width >= 768 && <div>Desktop layout</div>}
145
</div>
146
);
147
}
148
```
149
150
### useMutationObserver
151
152
Mutation Observer API for monitoring DOM changes.
153
154
```typescript { .api }
155
/**
156
* Mutation Observer API for DOM change monitoring
157
* @param callback - Function called when mutations occur
158
* @param options - MutationObserver configuration options
159
* @returns Ref callback to attach to target element
160
*/
161
function useMutationObserver<T extends HTMLElement = any>(
162
callback: MutationCallback,
163
options?: MutationObserverInit
164
): React.RefCallback<T | null>;
165
```
166
167
**Usage Examples:**
168
169
```typescript
170
import { useMutationObserver } from "@mantine/hooks";
171
172
function DynamicContentWatcher() {
173
const [changeCount, setChangeCount] = useState(0);
174
175
const ref = useMutationObserver<HTMLDivElement>(
176
(mutations) => {
177
setChangeCount(prev => prev + mutations.length);
178
},
179
{
180
childList: true,
181
subtree: true,
182
attributes: true,
183
characterData: true,
184
}
185
);
186
187
const addContent = () => {
188
const element = ref.current;
189
if (element) {
190
const newElement = document.createElement('p');
191
newElement.textContent = `Added at ${new Date().toLocaleTimeString()}`;
192
element.appendChild(newElement);
193
}
194
};
195
196
return (
197
<div>
198
<button onClick={addContent}>Add Content</button>
199
<p>Changes detected: {changeCount}</p>
200
<div ref={ref}>
201
<p>Original content</p>
202
</div>
203
</div>
204
);
205
}
206
```
207
208
### useInViewport
209
210
Simple viewport visibility detection.
211
212
```typescript { .api }
213
/**
214
* Simple viewport visibility detection
215
* @param options - IntersectionObserver options
216
* @returns Object with ref and visibility state
217
*/
218
function useInViewport<T extends HTMLElement = any>(
219
options?: IntersectionObserverInit
220
): UseInViewportReturnValue<T>;
221
222
interface UseInViewportReturnValue<T extends HTMLElement = any> {
223
ref: React.RefCallback<T | null>;
224
inViewport: boolean;
225
}
226
```
227
228
**Usage Examples:**
229
230
```typescript
231
import { useInViewport } from "@mantine/hooks";
232
233
function FadeInOnScroll({ children }: { children: React.ReactNode }) {
234
const { ref, inViewport } = useInViewport({
235
threshold: 0.3,
236
});
237
238
return (
239
<div
240
ref={ref}
241
style={{
242
opacity: inViewport ? 1 : 0,
243
transform: inViewport ? 'translateY(0)' : 'translateY(20px)',
244
transition: 'opacity 0.6s ease, transform 0.6s ease',
245
}}
246
>
247
{children}
248
</div>
249
);
250
}
251
252
// Analytics tracking
253
function AnalyticsTracker({ eventName }: { eventName: string }) {
254
const { ref, inViewport } = useInViewport({
255
threshold: 0.5,
256
});
257
258
useEffect(() => {
259
if (inViewport) {
260
analytics.track(`${eventName}_viewed`);
261
}
262
}, [inViewport, eventName]);
263
264
return <div ref={ref}>Tracked content</div>;
265
}
266
```
267
268
## Observer Patterns
269
270
### Lazy Loading with Intersection Observer
271
272
```typescript
273
import { useIntersection } from "@mantine/hooks";
274
275
function LazyLoadContainer({ items }: { items: any[] }) {
276
const [visibleItems, setVisibleItems] = useState(items.slice(0, 10));
277
const { ref, entry } = useIntersection({
278
threshold: 0.1,
279
rootMargin: '100px', // Load before reaching the bottom
280
});
281
282
useEffect(() => {
283
if (entry?.isIntersecting && visibleItems.length < items.length) {
284
setVisibleItems(prev => [
285
...prev,
286
...items.slice(prev.length, prev.length + 10)
287
]);
288
}
289
}, [entry?.isIntersecting, visibleItems.length, items]);
290
291
return (
292
<div>
293
{visibleItems.map((item, index) => (
294
<ItemComponent key={item.id} {...item} />
295
))}
296
{visibleItems.length < items.length && (
297
<div ref={ref} style={{ height: '50px', textAlign: 'center' }}>
298
Loading more items...
299
</div>
300
)}
301
</div>
302
);
303
}
304
```
305
306
### Responsive Layout with Size Observer
307
308
```typescript
309
import { useElementSize } from "@mantine/hooks";
310
311
function ResponsiveGrid({ items }: { items: any[] }) {
312
const { ref, width } = useElementSize();
313
314
const getColumns = (containerWidth: number) => {
315
if (containerWidth < 600) return 1;
316
if (containerWidth < 900) return 2;
317
if (containerWidth < 1200) return 3;
318
return 4;
319
};
320
321
const columns = getColumns(width);
322
323
return (
324
<div
325
ref={ref}
326
style={{
327
display: 'grid',
328
gridTemplateColumns: `repeat(${columns}, 1fr)`,
329
gap: '16px',
330
}}
331
>
332
{items.map(item => (
333
<GridItem key={item.id} {...item} />
334
))}
335
</div>
336
);
337
}
338
```
339
340
### Content Change Detection
341
342
```typescript
343
import { useMutationObserver } from "@mantine/hooks";
344
345
function ContentChangeTracker({ children }: { children: React.ReactNode }) {
346
const [lastChange, setLastChange] = useState<Date | null>(null);
347
const [changeLog, setChangeLog] = useState<string[]>([]);
348
349
const ref = useMutationObserver<HTMLDivElement>(
350
(mutations) => {
351
const now = new Date();
352
setLastChange(now);
353
354
const changes = mutations.map(mutation => {
355
switch (mutation.type) {
356
case 'childList':
357
return `Child nodes changed: +${mutation.addedNodes.length} -${mutation.removedNodes.length}`;
358
case 'attributes':
359
return `Attribute "${mutation.attributeName}" changed`;
360
case 'characterData':
361
return 'Text content changed';
362
default:
363
return 'Unknown change';
364
}
365
});
366
367
setChangeLog(prev => [...prev.slice(-9), ...changes]);
368
},
369
{
370
childList: true,
371
attributes: true,
372
characterData: true,
373
subtree: true,
374
}
375
);
376
377
return (
378
<div>
379
<div style={{ marginBottom: '16px', padding: '8px', background: '#f5f5f5' }}>
380
<h3>Change Log</h3>
381
<p>Last change: {lastChange?.toLocaleTimeString() || 'None'}</p>
382
<ul style={{ maxHeight: '100px', overflow: 'auto' }}>
383
{changeLog.map((change, index) => (
384
<li key={index}>{change}</li>
385
))}
386
</ul>
387
</div>
388
389
<div ref={ref}>
390
{children}
391
</div>
392
</div>
393
);
394
}
395
```