or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

browser-apis.mddevice.mddom-events.mdindex.mdnavigation.mdnetwork.mdobservers.mdspecialized.mdstate-management.mdstorage.mdtiming.mdutilities.md

observers.mddocs/

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

```