0
# Lifecycle & Utilities
1
2
React lifecycle utilities and helper hooks for component lifecycle management, ref handling, and common utility patterns.
3
4
## Capabilities
5
6
### useForceUpdate
7
8
Force component re-render when needed.
9
10
```typescript { .api }
11
/**
12
* Force component re-render
13
* @returns Function to trigger re-render
14
*/
15
function useForceUpdate(): () => void;
16
```
17
18
**Usage Examples:**
19
20
```typescript
21
import { useForceUpdate } from "@mantine/hooks";
22
23
function ComponentWithExternalState() {
24
const forceUpdate = useForceUpdate();
25
26
// Force update when external state changes
27
useEffect(() => {
28
const unsubscribe = externalStore.subscribe(() => {
29
forceUpdate();
30
});
31
return unsubscribe;
32
}, [forceUpdate]);
33
34
return <div>{externalStore.getValue()}</div>;
35
}
36
```
37
38
### useId
39
40
Generate unique IDs with SSR support and optional static override.
41
42
```typescript { .api }
43
/**
44
* Generate unique IDs with SSR support
45
* @param staticId - Static ID to use instead of generated one
46
* @returns Unique ID string
47
*/
48
function useId(staticId?: string): string;
49
```
50
51
**Usage Examples:**
52
53
```typescript
54
import { useId } from "@mantine/hooks";
55
56
function FormField({ label, staticId }: { label: string; staticId?: string }) {
57
const id = useId(staticId);
58
59
return (
60
<div>
61
<label htmlFor={id}>{label}</label>
62
<input id={id} />
63
</div>
64
);
65
}
66
67
// Multiple IDs in same component
68
function MultiFieldForm() {
69
const nameId = useId();
70
const emailId = useId();
71
72
return (
73
<form>
74
<label htmlFor={nameId}>Name</label>
75
<input id={nameId} />
76
77
<label htmlFor={emailId}>Email</label>
78
<input id={emailId} />
79
</form>
80
);
81
}
82
```
83
84
### useMounted
85
86
Track component mount state.
87
88
```typescript { .api }
89
/**
90
* Track component mount state
91
* @returns Boolean indicating if component is mounted
92
*/
93
function useMounted(): boolean;
94
```
95
96
**Usage Examples:**
97
98
```typescript
99
import { useMounted } from "@mantine/hooks";
100
101
function AsyncComponent() {
102
const [data, setData] = useState(null);
103
const mounted = useMounted();
104
105
useEffect(() => {
106
fetchData().then(result => {
107
// Only update state if component is still mounted
108
if (mounted) {
109
setData(result);
110
}
111
});
112
}, [mounted]);
113
114
return <div>{data ? 'Loaded' : 'Loading...'}</div>;
115
}
116
```
117
118
### useIsFirstRender
119
120
Detect if current render is the first render.
121
122
```typescript { .api }
123
/**
124
* Detect first render
125
* @returns Boolean indicating if this is the first render
126
*/
127
function useIsFirstRender(): boolean;
128
```
129
130
**Usage Examples:**
131
132
```typescript
133
import { useIsFirstRender } from "@mantine/hooks";
134
135
function AnimatedComponent() {
136
const isFirstRender = useIsFirstRender();
137
138
return (
139
<div
140
className={isFirstRender ? 'no-animation' : 'with-animation'}
141
>
142
Content
143
</div>
144
);
145
}
146
```
147
148
### usePrevious
149
150
Access the previous value of a variable.
151
152
```typescript { .api }
153
/**
154
* Access previous value
155
* @param value - Current value
156
* @returns Previous value or undefined on first render
157
*/
158
function usePrevious<T>(value: T): T | undefined;
159
```
160
161
**Usage Examples:**
162
163
```typescript
164
import { usePrevious } from "@mantine/hooks";
165
166
function Counter() {
167
const [count, setCount] = useState(0);
168
const previousCount = usePrevious(count);
169
170
return (
171
<div>
172
<p>Current: {count}</p>
173
<p>Previous: {previousCount ?? 'None'}</p>
174
<button onClick={() => setCount(c => c + 1)}>
175
Increment
176
</button>
177
</div>
178
);
179
}
180
181
// Track prop changes
182
function UserProfile({ userId }: { userId: string }) {
183
const previousUserId = usePrevious(userId);
184
185
useEffect(() => {
186
if (previousUserId && previousUserId !== userId) {
187
console.log(`User changed from ${previousUserId} to ${userId}`);
188
}
189
}, [userId, previousUserId]);
190
191
return <div>User ID: {userId}</div>;
192
}
193
```
194
195
### useDidUpdate
196
197
Effect that skips the first render (componentDidUpdate equivalent).
198
199
```typescript { .api }
200
/**
201
* Effect that skips first render
202
* @param fn - Effect function
203
* @param dependencies - Dependency array
204
*/
205
function useDidUpdate(fn: React.EffectCallback, dependencies?: React.DependencyList): void;
206
```
207
208
**Usage Examples:**
209
210
```typescript
211
import { useDidUpdate } from "@mantine/hooks";
212
213
function SearchComponent({ query }: { query: string }) {
214
const [results, setResults] = useState([]);
215
216
// Only run search on updates, not on mount
217
useDidUpdate(() => {
218
if (query) {
219
searchAPI(query).then(setResults);
220
}
221
}, [query]);
222
223
return <SearchResults results={results} />;
224
}
225
```
226
227
### useIsomorphicEffect
228
229
useLayoutEffect in browser, useEffect in SSR environments.
230
231
```typescript { .api }
232
/**
233
* Isomorphic effect (useLayoutEffect in browser, useEffect in SSR)
234
* @param effect - Effect function
235
* @param deps - Dependency array
236
*/
237
function useIsomorphicEffect(effect: React.EffectCallback, deps?: React.DependencyList): void;
238
```
239
240
### useShallowEffect
241
242
Effect with shallow dependency comparison instead of reference equality.
243
244
```typescript { .api }
245
/**
246
* Effect with shallow dependency comparison
247
* @param callback - Effect function
248
* @param dependencies - Dependency array (compared shallowly)
249
*/
250
function useShallowEffect(callback: React.EffectCallback, dependencies: React.DependencyList): void;
251
```
252
253
**Usage Examples:**
254
255
```typescript
256
import { useShallowEffect } from "@mantine/hooks";
257
258
function ObjectDependentComponent({ config }: { config: Config }) {
259
const [data, setData] = useState(null);
260
261
// Only re-run when config properties change, not reference
262
useShallowEffect(() => {
263
fetchDataWithConfig(config).then(setData);
264
}, [config]);
265
266
return <div>{data}</div>;
267
}
268
```
269
270
### useMergedRef
271
272
Merge multiple refs into a single ref callback.
273
274
```typescript { .api }
275
/**
276
* Merge multiple refs into single ref callback
277
* @param refs - Array of refs to merge
278
* @returns Single ref callback
279
*/
280
function useMergedRef<T>(...refs: React.Ref<T>[]): React.RefCallback<T | null>;
281
282
/**
283
* Merge refs utility function
284
* @param refs - Array of refs to merge
285
* @returns Single ref callback
286
*/
287
function mergeRefs<T>(...refs: React.Ref<T>[]): React.RefCallback<T | null>;
288
289
/**
290
* Assign value to ref utility
291
* @param ref - Ref to assign to
292
* @param value - Value to assign
293
*/
294
function assignRef<T>(ref: React.Ref<T> | undefined, value: T): void;
295
```
296
297
**Usage Examples:**
298
299
```typescript
300
import { useMergedRef } from "@mantine/hooks";
301
import { useRef, forwardRef } from "react";
302
303
// Merge internal ref with forwarded ref
304
const CustomInput = forwardRef<HTMLInputElement, Props>((props, forwardedRef) => {
305
const internalRef = useRef<HTMLInputElement>(null);
306
const mergedRef = useMergedRef(internalRef, forwardedRef);
307
308
useEffect(() => {
309
// Use internal ref for internal logic
310
if (internalRef.current) {
311
internalRef.current.focus();
312
}
313
}, []);
314
315
return <input ref={mergedRef} {...props} />;
316
});
317
318
// Merge multiple hook refs
319
function MultiRefComponent() {
320
const hoverRef = useHover().ref;
321
const clickOutsideRef = useClickOutside(() => {});
322
const focusWithinRef = useFocusWithin().ref;
323
324
const mergedRef = useMergedRef(hoverRef, clickOutsideRef, focusWithinRef);
325
326
return <div ref={mergedRef}>Multiple behaviors</div>;
327
}
328
```
329
330
## Utility Functions
331
332
Core utility functions exported by the package.
333
334
```typescript { .api }
335
/**
336
* Clamp value between min and max
337
* @param value - Value to clamp
338
* @param min - Minimum value
339
* @param max - Maximum value
340
* @returns Clamped value
341
*/
342
function clamp(value: number, min: number, max: number): number;
343
344
/**
345
* Generate random ID string
346
* @returns Random ID
347
*/
348
function randomId(): string;
349
350
/**
351
* Create array of numbers in range
352
* @param start - Start number (inclusive)
353
* @param end - End number (exclusive)
354
* @returns Array of numbers
355
*/
356
function range(start: number, end: number): number[];
357
358
/**
359
* Shallow equality comparison
360
* @param a - First value
361
* @param b - Second value
362
* @returns Boolean indicating shallow equality
363
*/
364
function shallowEqual(a: any, b: any): boolean;
365
366
/**
367
* Convert first character to uppercase
368
* @param string - Input string
369
* @returns String with first character uppercased
370
*/
371
function upperFirst(string: string): string;
372
373
/**
374
* Convert first character to lowercase
375
* @param string - Input string
376
* @returns String with first character lowercased
377
*/
378
function lowerFirst(string: string): string;
379
380
/**
381
* Ref callback hook utility
382
* @param callback - Callback to convert to ref
383
* @returns Ref callback
384
*/
385
function useCallbackRef<T>(callback: T | undefined): T;
386
```
387
388
**Usage Examples:**
389
390
```typescript
391
import { clamp, randomId, range, shallowEqual, upperFirst } from "@mantine/hooks";
392
393
// Mathematical utilities
394
const percentage = clamp(userInput, 0, 100); // Ensure 0-100 range
395
const numbers = range(1, 10); // [1, 2, 3, 4, 5, 6, 7, 8, 9]
396
397
// String utilities
398
const title = upperFirst('hello world'); // "Hello world"
399
400
// Comparison
401
const hasChanged = !shallowEqual(prevProps, nextProps);
402
403
// Unique IDs
404
const elementId = randomId(); // "mantine-r4nd0m1d"
405
```
406
407
## Common Patterns
408
409
### Ref Management
410
411
```typescript
412
import { useMergedRef, useCallbackRef } from "@mantine/hooks";
413
414
function ComplexComponent({ onResize, onClick }: Props) {
415
const resizeRef = useResizeObserver(onResize);
416
const clickRef = useClickOutside(onClick);
417
const internalRef = useRef<HTMLDivElement>(null);
418
419
// Merge all refs
420
const mergedRef = useMergedRef(resizeRef, clickRef, internalRef);
421
422
return <div ref={mergedRef}>Complex component</div>;
423
}
424
```
425
426
### Lifecycle Management
427
428
```typescript
429
import { useMounted, useDidUpdate, usePrevious } from "@mantine/hooks";
430
431
function DataComponent({ dataId }: { dataId: string }) {
432
const [data, setData] = useState(null);
433
const [loading, setLoading] = useState(false);
434
const mounted = useMounted();
435
const previousDataId = usePrevious(dataId);
436
437
// Load data on mount
438
useEffect(() => {
439
loadData(dataId);
440
}, []);
441
442
// Reload data when ID changes (skip first render)
443
useDidUpdate(() => {
444
if (previousDataId !== dataId) {
445
loadData(dataId);
446
}
447
}, [dataId, previousDataId]);
448
449
const loadData = async (id: string) => {
450
setLoading(true);
451
try {
452
const result = await fetchData(id);
453
if (mounted) {
454
setData(result);
455
}
456
} finally {
457
if (mounted) {
458
setLoading(false);
459
}
460
}
461
};
462
463
return loading ? <div>Loading...</div> : <div>{data}</div>;
464
}
465
```