0
# Hook Tracking
1
2
Advanced hook tracking system for monitoring React hooks like useState, useReducer, and custom hooks to detect unnecessary re-renders caused by hook state changes.
3
4
## Capabilities
5
6
### Hook Tracking Configuration
7
8
Built-in configuration for tracking standard React hooks:
9
10
```typescript { .api }
11
interface HookConfig {
12
/** Path to the tracked value within the hook result */
13
path?: string;
14
15
/** Path to dependencies array for memoization hooks */
16
dependenciesPath?: string;
17
18
/** Whether to suppress reporting for this hook type */
19
dontReport?: boolean;
20
}
21
22
/**
23
* Built-in hook tracking configuration
24
*/
25
const hooksConfig: {
26
useState: { path: '0' };
27
useReducer: { path: '0' };
28
useContext: undefined;
29
useSyncExternalStore: undefined;
30
useMemo: { dependenciesPath: '1', dontReport: true };
31
useCallback: { dependenciesPath: '1', dontReport: true };
32
};
33
```
34
35
### Extra Hook Tracking
36
37
Support for tracking additional hooks beyond the built-in React hooks:
38
39
```typescript { .api }
40
/**
41
* Configuration for tracking additional hooks
42
* First element is the hook parent object, second is the hook name
43
*/
44
type ExtraHookToTrack = [any, string];
45
```
46
47
**Usage Examples:**
48
49
```javascript
50
import whyDidYouRender from '@welldone-software/why-did-you-render';
51
import { useSelector } from 'react-redux';
52
53
// Track React-Redux useSelector hook
54
whyDidYouRender(React, {
55
trackHooks: true,
56
trackExtraHooks: [
57
[require('react-redux'), 'useSelector']
58
]
59
});
60
61
// Track custom hooks from your own library
62
import * as myHooks from './my-custom-hooks';
63
64
whyDidYouRender(React, {
65
trackHooks: true,
66
trackExtraHooks: [
67
[myHooks, 'useCustomState'],
68
[myHooks, 'useAsyncData']
69
]
70
});
71
```
72
73
### Hook Change Detection
74
75
System for detecting and analyzing hook state changes:
76
77
```typescript { .api }
78
interface HookDifference {
79
/** Path string to the changed value (e.g., "0" for useState result) */
80
pathString: string;
81
82
/** Type of difference detected (e.g., "different", "deepEquals", "same") */
83
diffType: string;
84
85
/** Previous hook value before the change */
86
prevValue: any;
87
88
/** Next hook value after the change */
89
nextValue: any;
90
}
91
```
92
93
### Hook Tracking Function
94
95
Internal function that wraps hooks for tracking (exposed for advanced usage):
96
97
```typescript { .api }
98
/**
99
* Tracks changes in hook results and reports unnecessary re-renders
100
* @param hookName - Name of the hook being tracked
101
* @param hookTrackingConfig - Configuration for how to track this hook
102
* @param rawHookResult - The raw result returned by the hook
103
* @returns The unmodified hook result
104
*/
105
function trackHookChanges(
106
hookName: string,
107
hookTrackingConfig: { path?: string },
108
rawHookResult: any
109
): any;
110
```
111
112
### Hook Information Storage
113
114
Data structures for storing hook information during render cycles:
115
116
```typescript { .api }
117
interface HookInfo {
118
/** Name of the hook */
119
hookName: string;
120
121
/** Current result/value of the hook */
122
result: any;
123
}
124
125
/**
126
* Map that stores hook information for the current render cycle
127
* Keys are component instances, values are arrays of HookInfo
128
*/
129
type HooksInfoMap = WeakMap<React.Component, HookInfo[]>;
130
```
131
132
## Hook Integration Examples
133
134
### useState Tracking
135
136
```javascript
137
import React, { useState } from 'react';
138
import whyDidYouRender from '@welldone-software/why-did-you-render';
139
140
whyDidYouRender(React, {
141
trackHooks: true
142
});
143
144
const Counter = () => {
145
const [count, setCount] = useState(0);
146
const [name, setName] = useState('Counter');
147
148
return (
149
<div>
150
<h1>{name}: {count}</h1>
151
<button onClick={() => setCount(count + 1)}>Increment</button>
152
{/* This will trigger a warning if name doesn't actually change */}
153
<button onClick={() => setName('Counter')}>Set Same Name</button>
154
</div>
155
);
156
};
157
158
Counter.whyDidYouRender = true;
159
```
160
161
### useSelector Tracking (React-Redux)
162
163
```javascript
164
import React from 'react';
165
import { useSelector } from 'react-redux';
166
import whyDidYouRender from '@welldone-software/why-did-you-render';
167
168
whyDidYouRender(React, {
169
trackHooks: true,
170
trackExtraHooks: [
171
[require('react-redux'), 'useSelector']
172
]
173
});
174
175
const UserProfile = ({ userId }) => {
176
// This will be tracked for unnecessary re-renders
177
const user = useSelector(state => state.users[userId]);
178
const isLoading = useSelector(state => state.ui.loading);
179
180
if (isLoading) return <div>Loading...</div>;
181
182
return (
183
<div>
184
<h1>{user.name}</h1>
185
<p>{user.email}</p>
186
</div>
187
);
188
};
189
190
UserProfile.whyDidYouRender = true;
191
```
192
193
### Custom Hook Tracking
194
195
```javascript
196
import React, { useState, useEffect } from 'react';
197
import whyDidYouRender from '@welldone-software/why-did-you-render';
198
199
// Custom hook
200
const useApi = (url) => {
201
const [data, setData] = useState(null);
202
const [loading, setLoading] = useState(true);
203
204
useEffect(() => {
205
fetch(url)
206
.then(response => response.json())
207
.then(result => {
208
setData(result);
209
setLoading(false);
210
});
211
}, [url]);
212
213
return { data, loading };
214
};
215
216
// Track the custom hook
217
whyDidYouRender(React, {
218
trackHooks: true,
219
trackExtraHooks: [
220
[{ useApi }, 'useApi']
221
]
222
});
223
224
const DataComponent = ({ endpoint }) => {
225
const { data, loading } = useApi(endpoint);
226
227
if (loading) return <div>Loading...</div>;
228
return <div>{JSON.stringify(data)}</div>;
229
};
230
231
DataComponent.whyDidYouRender = true;
232
```
233
234
## Hook Tracking Configuration Options
235
236
### Enabling Hook Tracking
237
238
```typescript { .api }
239
interface HookTrackingOptions {
240
/** Whether to track React hooks for state changes */
241
trackHooks?: boolean;
242
243
/** Additional hooks to track beyond built-in React hooks */
244
trackExtraHooks?: Array<ExtraHookToTrack>;
245
}
246
```
247
248
### Hook-Specific Configuration
249
250
```javascript
251
// Example of comprehensive hook tracking setup
252
whyDidYouRender(React, {
253
trackHooks: true,
254
trackExtraHooks: [
255
// Track React-Redux hooks
256
[require('react-redux'), 'useSelector'],
257
[require('react-redux'), 'useDispatch'],
258
259
// Track React Router hooks
260
[require('react-router-dom'), 'useParams'],
261
[require('react-router-dom'), 'useLocation'],
262
263
// Track custom application hooks
264
[require('./hooks/useAuth'), 'useAuth'],
265
[require('./hooks/useApi'), 'useApi']
266
]
267
});
268
```
269
270
## Hook Difference Types
271
272
The library categorizes hook changes into different types:
273
274
```typescript { .api }
275
/**
276
* Types of differences that can be detected in hook values
277
*/
278
enum DiffTypes {
279
/** Values are identical (===) */
280
same = 'same',
281
282
/** Values are different */
283
different = 'different',
284
285
/** Values are deeply equal but not identical */
286
deepEquals = 'deepEquals'
287
}
288
```
289
290
## Advanced Hook Features
291
292
### Dependency Tracking
293
294
For memoization hooks like `useMemo` and `useCallback`, the library tracks dependencies:
295
296
```javascript
297
const MyComponent = ({ items }) => {
298
// Dependencies array [items.length] will be tracked
299
const expensiveValue = useMemo(() => {
300
return items.reduce((sum, item) => sum + item.value, 0);
301
}, [items.length]); // This dependency array is monitored
302
303
return <div>{expensiveValue}</div>;
304
};
305
```
306
307
### Hook Result Path Tracking
308
309
For hooks that return arrays or objects, specific paths can be tracked:
310
311
```javascript
312
// useState returns [value, setter] - path "0" tracks the value
313
const [count, setCount] = useState(0); // Tracks count changes
314
315
// useReducer returns [state, dispatch] - path "0" tracks the state
316
const [state, dispatch] = useReducer(reducer, initialState); // Tracks state changes
317
```
318
319
### Hook Name Customization
320
321
You can provide custom names for better debugging:
322
323
```javascript
324
whyDidYouRender(React, {
325
trackHooks: true,
326
trackExtraHooks: [
327
[myHooksLibrary, 'useComplexState', { customName: 'ComplexStateHook' }]
328
]
329
});
330
```