npm-react

Description
React is a JavaScript library for building user interfaces with declarative, component-based architecture.
Author
tessl
Last updated

How to use

npx @tessl/cli registry install tessl/npm-react@18.3.0

hoc.md docs/

1
# Higher-Order Components
2
3
Higher-order components (HOCs) are functions that take a component and return a new component with enhanced functionality. React provides several built-in HOCs for common patterns like memoization, ref forwarding, and lazy loading.
4
5
## Capabilities
6
7
### memo
8
9
Memoizes a component to prevent unnecessary re-renders when props haven't changed.
10
11
```javascript { .api }
12
/**
13
* Memoizes component with shallow prop comparison
14
* @param Component - Function component to memoize
15
* @param propsAreEqual - Optional custom comparison function
16
* @returns Memoized component
17
*/
18
function memo<P extends object>(
19
Component: FunctionComponent<P>,
20
propsAreEqual?: (prevProps: Readonly<P>, nextProps: Readonly<P>) => boolean
21
): NamedExoticComponent<P>;
22
```
23
24
**Usage Examples:**
25
26
```javascript
27
import React, { memo, useState } from 'react';
28
29
// Basic memoization
30
const ExpensiveComponent = memo(function ExpensiveComponent({ data, options }) {
31
console.log('ExpensiveComponent rendering'); // Only logs when props change
32
33
const processedData = useMemo(() => {
34
return data.map(item => processExpensiveCalculation(item, options));
35
}, [data, options]);
36
37
return (
38
<div>
39
{processedData.map(item => (
40
<div key={item.id}>{item.result}</div>
41
))}
42
</div>
43
);
44
});
45
46
// Custom comparison function
47
const UserCard = memo(function UserCard({ user, theme, onEdit }) {
48
return (
49
<div className={`user-card theme-${theme}`}>
50
<h3>{user.name}</h3>
51
<p>{user.email}</p>
52
<button onClick={() => onEdit(user.id)}>Edit</button>
53
</div>
54
);
55
}, (prevProps, nextProps) => {
56
// Custom comparison - only re-render if user data or theme changed
57
return (
58
prevProps.user.id === nextProps.user.id &&
59
prevProps.user.name === nextProps.user.name &&
60
prevProps.user.email === nextProps.user.email &&
61
prevProps.theme === nextProps.theme
62
// Ignore onEdit function changes
63
);
64
});
65
66
// Memoizing with complex props
67
const DataVisualization = memo(function DataVisualization({
68
dataset,
69
config,
70
width,
71
height
72
}) {
73
const chartData = useMemo(() => {
74
return transformDataForChart(dataset, config);
75
}, [dataset, config]);
76
77
return (
78
<svg width={width} height={height}>
79
{/* Complex visualization rendering */}
80
</svg>
81
);
82
}, (prevProps, nextProps) => {
83
// Deep comparison for complex objects
84
return (
85
JSON.stringify(prevProps.dataset) === JSON.stringify(nextProps.dataset) &&
86
JSON.stringify(prevProps.config) === JSON.stringify(nextProps.config) &&
87
prevProps.width === nextProps.width &&
88
prevProps.height === nextProps.height
89
);
90
});
91
92
// Usage
93
function App() {
94
const [users, setUsers] = useState([]);
95
const [selectedUser, setSelectedUser] = useState(null);
96
const [theme, setTheme] = useState('light');
97
98
return (
99
<div>
100
{users.map(user => (
101
<UserCard
102
key={user.id}
103
user={user}
104
theme={theme}
105
onEdit={setSelectedUser} // This function reference changes, but memo ignores it
106
/>
107
))}
108
</div>
109
);
110
}
111
```
112
113
### forwardRef
114
115
Forwards refs through components to access DOM elements or component instances.
116
117
```javascript { .api }
118
/**
119
* Forwards refs through component hierarchy
120
* @param render - Function component that receives props and ref
121
* @returns Component that can receive refs
122
*/
123
function forwardRef<T, P = {}>(
124
render: ForwardRefRenderFunction<T, P>
125
): ForwardRefExoticComponent<PropsWithoutRef<P> & RefAttributes<T>>;
126
127
type ForwardRefRenderFunction<T, P = {}> = (
128
props: P,
129
ref: ForwardedRef<T>
130
) => ReactElement | null;
131
```
132
133
**Usage Examples:**
134
135
```javascript
136
import React, { forwardRef, useRef, useImperativeHandle } from 'react';
137
138
// Basic ref forwarding to DOM element
139
const CustomInput = forwardRef(function CustomInput(props, ref) {
140
return (
141
<div className="custom-input-wrapper">
142
<input
143
ref={ref}
144
className="custom-input"
145
{...props}
146
/>
147
</div>
148
);
149
});
150
151
// Usage
152
function LoginForm() {
153
const emailInputRef = useRef(null);
154
155
const handleSubmit = (e) => {
156
e.preventDefault();
157
if (!emailInputRef.current.value) {
158
emailInputRef.current.focus(); // Direct DOM access
159
}
160
};
161
162
return (
163
<form onSubmit={handleSubmit}>
164
<CustomInput
165
ref={emailInputRef}
166
type="email"
167
placeholder="Enter email"
168
/>
169
<button type="submit">Login</button>
170
</form>
171
);
172
}
173
174
// Custom imperative API with useImperativeHandle
175
const CustomModal = forwardRef(function CustomModal({ children, title }, ref) {
176
const [isOpen, setIsOpen] = useState(false);
177
const modalRef = useRef(null);
178
179
useImperativeHandle(ref, () => ({
180
open: () => setIsOpen(true),
181
close: () => setIsOpen(false),
182
toggle: () => setIsOpen(prev => !prev),
183
focus: () => modalRef.current?.focus(),
184
isOpen: isOpen
185
}), [isOpen]);
186
187
if (!isOpen) return null;
188
189
return (
190
<div className="modal-overlay" onClick={() => setIsOpen(false)}>
191
<div
192
ref={modalRef}
193
className="modal-content"
194
tabIndex={-1}
195
onClick={(e) => e.stopPropagation()}
196
>
197
<h2>{title}</h2>
198
{children}
199
<button onClick={() => setIsOpen(false)}>Close</button>
200
</div>
201
</div>
202
);
203
});
204
205
// Usage with imperative API
206
function App() {
207
const modalRef = useRef(null);
208
209
const openModal = () => {
210
modalRef.current?.open();
211
};
212
213
const checkModalState = () => {
214
console.log('Modal is open:', modalRef.current?.isOpen);
215
};
216
217
return (
218
<div>
219
<button onClick={openModal}>Open Modal</button>
220
<button onClick={checkModalState}>Check Modal State</button>
221
222
<CustomModal ref={modalRef} title="Important Notice">
223
<p>This is modal content</p>
224
</CustomModal>
225
</div>
226
);
227
}
228
229
// Higher-order component with ref forwarding
230
function withErrorBoundary(Component) {
231
return forwardRef(function WithErrorBoundary(props, ref) {
232
return (
233
<ErrorBoundary>
234
<Component {...props} ref={ref} />
235
</ErrorBoundary>
236
);
237
});
238
}
239
240
const SafeCustomInput = withErrorBoundary(CustomInput);
241
```
242
243
### lazy
244
245
Enables code splitting by dynamically importing components.
246
247
```javascript { .api }
248
/**
249
* Lazy load components with dynamic imports
250
* @param factory - Function that returns dynamic import promise
251
* @returns Lazy component that can be used with Suspense
252
*/
253
function lazy<T extends ComponentType<any>>(
254
factory: () => Promise<{ default: T }>
255
): LazyExoticComponent<T>;
256
```
257
258
**Usage Examples:**
259
260
```javascript
261
import React, { lazy, Suspense, useState } from 'react';
262
263
// Basic lazy loading
264
const LazyDashboard = lazy(() => import('./Dashboard'));
265
const LazySettings = lazy(() => import('./Settings'));
266
const LazyProfile = lazy(() => import('./Profile'));
267
268
function App() {
269
const [currentView, setCurrentView] = useState('dashboard');
270
271
const renderView = () => {
272
switch (currentView) {
273
case 'dashboard':
274
return <LazyDashboard />;
275
case 'settings':
276
return <LazySettings />;
277
case 'profile':
278
return <LazyProfile />;
279
default:
280
return <div>Page not found</div>;
281
}
282
};
283
284
return (
285
<div>
286
<nav>
287
<button onClick={() => setCurrentView('dashboard')}>Dashboard</button>
288
<button onClick={() => setCurrentView('settings')}>Settings</button>
289
<button onClick={() => setCurrentView('profile')}>Profile</button>
290
</nav>
291
292
<Suspense fallback={<div>Loading...</div>}>
293
{renderView()}
294
</Suspense>
295
</div>
296
);
297
}
298
299
// Conditional lazy loading
300
const HeavyChart = lazy(() => {
301
// Only load if user has premium features
302
if (user.isPremium) {
303
return import('./HeavyChart');
304
} else {
305
return import('./BasicChart');
306
}
307
});
308
309
// Lazy loading with error handling
310
const LazyComponent = lazy(async () => {
311
try {
312
const module = await import('./ExpensiveComponent');
313
return module;
314
} catch (error) {
315
console.error('Failed to load component:', error);
316
// Return fallback component
317
return import('./FallbackComponent');
318
}
319
});
320
321
// Route-based code splitting
322
const Home = lazy(() => import('./pages/Home'));
323
const About = lazy(() => import('./pages/About'));
324
const Contact = lazy(() => import('./pages/Contact'));
325
const AdminPanel = lazy(() => import('./pages/AdminPanel'));
326
327
function AppRouter() {
328
return (
329
<Router>
330
<Suspense fallback={<LoadingSpinner />}>
331
<Routes>
332
<Route path="/" element={<Home />} />
333
<Route path="/about" element={<About />} />
334
<Route path="/contact" element={<Contact />} />
335
<Route
336
path="/admin"
337
element={
338
<ProtectedRoute>
339
<AdminPanel />
340
</ProtectedRoute>
341
}
342
/>
343
</Routes>
344
</Suspense>
345
</Router>
346
);
347
}
348
349
// Preloading lazy components
350
const ProductCatalog = lazy(() => import('./ProductCatalog'));
351
352
function ProductButton() {
353
const [showCatalog, setShowCatalog] = useState(false);
354
355
// Preload on hover
356
const handleMouseEnter = () => {
357
import('./ProductCatalog'); // Preload without waiting
358
};
359
360
return (
361
<div>
362
<button
363
onMouseEnter={handleMouseEnter}
364
onClick={() => setShowCatalog(true)}
365
>
366
View Products
367
</button>
368
369
{showCatalog && (
370
<Suspense fallback={<div>Loading catalog...</div>}>
371
<ProductCatalog />
372
</Suspense>
373
)}
374
</div>
375
);
376
}
377
378
// Lazy loading with props
379
const LazyModalContent = lazy(() => import('./ModalContent'));
380
381
function Modal({ isOpen, contentType, ...props }) {
382
if (!isOpen) return null;
383
384
return (
385
<div className="modal">
386
<Suspense fallback={<div>Loading modal content...</div>}>
387
<LazyModalContent type={contentType} {...props} />
388
</Suspense>
389
</div>
390
);
391
}
392
```
393
394
## Types
395
396
```javascript { .api }
397
// Higher-order component types
398
type NamedExoticComponent<P = {}> = ExoticComponent<P> & {
399
displayName?: string;
400
};
401
402
type ForwardRefExoticComponent<P> = NamedExoticComponent<P> & {
403
$$typeof: symbol;
404
};
405
406
type LazyExoticComponent<T extends ComponentType<any>> = ExoticComponent<ComponentProps<T>> & {
407
$$typeof: symbol;
408
};
409
410
// Ref forwarding types
411
type ForwardedRef<T> = ((instance: T | null) => void) | MutableRefObject<T | null> | null;
412
413
type ForwardRefRenderFunction<T, P = {}> = (
414
props: P,
415
ref: ForwardedRef<T>
416
) => ReactElement | null;
417
418
type PropsWithoutRef<P> = Pick<P, Exclude<keyof P, 'ref'>>;
419
420
// Component prop extraction
421
type ComponentProps<T extends keyof JSX.IntrinsicElements | JSXElementConstructor<any>> =
422
T extends JSXElementConstructor<infer P>
423
? P
424
: T extends keyof JSX.IntrinsicElements
425
? JSX.IntrinsicElements[T]
426
: {};
427
```