- Spec files
npm-react
Describes: pkg:npm/react@18.3.x
- Description
- React is a JavaScript library for building user interfaces with declarative, component-based architecture.
- Author
- tessl
- Last updated
hoc.md docs/
1# Higher-Order Components23Higher-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.45## Capabilities67### memo89Memoizes a component to prevent unnecessary re-renders when props haven't changed.1011```javascript { .api }12/**13* Memoizes component with shallow prop comparison14* @param Component - Function component to memoize15* @param propsAreEqual - Optional custom comparison function16* @returns Memoized component17*/18function memo<P extends object>(19Component: FunctionComponent<P>,20propsAreEqual?: (prevProps: Readonly<P>, nextProps: Readonly<P>) => boolean21): NamedExoticComponent<P>;22```2324**Usage Examples:**2526```javascript27import React, { memo, useState } from 'react';2829// Basic memoization30const ExpensiveComponent = memo(function ExpensiveComponent({ data, options }) {31console.log('ExpensiveComponent rendering'); // Only logs when props change3233const processedData = useMemo(() => {34return data.map(item => processExpensiveCalculation(item, options));35}, [data, options]);3637return (38<div>39{processedData.map(item => (40<div key={item.id}>{item.result}</div>41))}42</div>43);44});4546// Custom comparison function47const UserCard = memo(function UserCard({ user, theme, onEdit }) {48return (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 changed57return (58prevProps.user.id === nextProps.user.id &&59prevProps.user.name === nextProps.user.name &&60prevProps.user.email === nextProps.user.email &&61prevProps.theme === nextProps.theme62// Ignore onEdit function changes63);64});6566// Memoizing with complex props67const DataVisualization = memo(function DataVisualization({68dataset,69config,70width,71height72}) {73const chartData = useMemo(() => {74return transformDataForChart(dataset, config);75}, [dataset, config]);7677return (78<svg width={width} height={height}>79{/* Complex visualization rendering */}80</svg>81);82}, (prevProps, nextProps) => {83// Deep comparison for complex objects84return (85JSON.stringify(prevProps.dataset) === JSON.stringify(nextProps.dataset) &&86JSON.stringify(prevProps.config) === JSON.stringify(nextProps.config) &&87prevProps.width === nextProps.width &&88prevProps.height === nextProps.height89);90});9192// Usage93function App() {94const [users, setUsers] = useState([]);95const [selectedUser, setSelectedUser] = useState(null);96const [theme, setTheme] = useState('light');9798return (99<div>100{users.map(user => (101<UserCard102key={user.id}103user={user}104theme={theme}105onEdit={setSelectedUser} // This function reference changes, but memo ignores it106/>107))}108</div>109);110}111```112113### forwardRef114115Forwards refs through components to access DOM elements or component instances.116117```javascript { .api }118/**119* Forwards refs through component hierarchy120* @param render - Function component that receives props and ref121* @returns Component that can receive refs122*/123function forwardRef<T, P = {}>(124render: ForwardRefRenderFunction<T, P>125): ForwardRefExoticComponent<PropsWithoutRef<P> & RefAttributes<T>>;126127type ForwardRefRenderFunction<T, P = {}> = (128props: P,129ref: ForwardedRef<T>130) => ReactElement | null;131```132133**Usage Examples:**134135```javascript136import React, { forwardRef, useRef, useImperativeHandle } from 'react';137138// Basic ref forwarding to DOM element139const CustomInput = forwardRef(function CustomInput(props, ref) {140return (141<div className="custom-input-wrapper">142<input143ref={ref}144className="custom-input"145{...props}146/>147</div>148);149});150151// Usage152function LoginForm() {153const emailInputRef = useRef(null);154155const handleSubmit = (e) => {156e.preventDefault();157if (!emailInputRef.current.value) {158emailInputRef.current.focus(); // Direct DOM access159}160};161162return (163<form onSubmit={handleSubmit}>164<CustomInput165ref={emailInputRef}166type="email"167placeholder="Enter email"168/>169<button type="submit">Login</button>170</form>171);172}173174// Custom imperative API with useImperativeHandle175const CustomModal = forwardRef(function CustomModal({ children, title }, ref) {176const [isOpen, setIsOpen] = useState(false);177const modalRef = useRef(null);178179useImperativeHandle(ref, () => ({180open: () => setIsOpen(true),181close: () => setIsOpen(false),182toggle: () => setIsOpen(prev => !prev),183focus: () => modalRef.current?.focus(),184isOpen: isOpen185}), [isOpen]);186187if (!isOpen) return null;188189return (190<div className="modal-overlay" onClick={() => setIsOpen(false)}>191<div192ref={modalRef}193className="modal-content"194tabIndex={-1}195onClick={(e) => e.stopPropagation()}196>197<h2>{title}</h2>198{children}199<button onClick={() => setIsOpen(false)}>Close</button>200</div>201</div>202);203});204205// Usage with imperative API206function App() {207const modalRef = useRef(null);208209const openModal = () => {210modalRef.current?.open();211};212213const checkModalState = () => {214console.log('Modal is open:', modalRef.current?.isOpen);215};216217return (218<div>219<button onClick={openModal}>Open Modal</button>220<button onClick={checkModalState}>Check Modal State</button>221222<CustomModal ref={modalRef} title="Important Notice">223<p>This is modal content</p>224</CustomModal>225</div>226);227}228229// Higher-order component with ref forwarding230function withErrorBoundary(Component) {231return forwardRef(function WithErrorBoundary(props, ref) {232return (233<ErrorBoundary>234<Component {...props} ref={ref} />235</ErrorBoundary>236);237});238}239240const SafeCustomInput = withErrorBoundary(CustomInput);241```242243### lazy244245Enables code splitting by dynamically importing components.246247```javascript { .api }248/**249* Lazy load components with dynamic imports250* @param factory - Function that returns dynamic import promise251* @returns Lazy component that can be used with Suspense252*/253function lazy<T extends ComponentType<any>>(254factory: () => Promise<{ default: T }>255): LazyExoticComponent<T>;256```257258**Usage Examples:**259260```javascript261import React, { lazy, Suspense, useState } from 'react';262263// Basic lazy loading264const LazyDashboard = lazy(() => import('./Dashboard'));265const LazySettings = lazy(() => import('./Settings'));266const LazyProfile = lazy(() => import('./Profile'));267268function App() {269const [currentView, setCurrentView] = useState('dashboard');270271const renderView = () => {272switch (currentView) {273case 'dashboard':274return <LazyDashboard />;275case 'settings':276return <LazySettings />;277case 'profile':278return <LazyProfile />;279default:280return <div>Page not found</div>;281}282};283284return (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>291292<Suspense fallback={<div>Loading...</div>}>293{renderView()}294</Suspense>295</div>296);297}298299// Conditional lazy loading300const HeavyChart = lazy(() => {301// Only load if user has premium features302if (user.isPremium) {303return import('./HeavyChart');304} else {305return import('./BasicChart');306}307});308309// Lazy loading with error handling310const LazyComponent = lazy(async () => {311try {312const module = await import('./ExpensiveComponent');313return module;314} catch (error) {315console.error('Failed to load component:', error);316// Return fallback component317return import('./FallbackComponent');318}319});320321// Route-based code splitting322const Home = lazy(() => import('./pages/Home'));323const About = lazy(() => import('./pages/About'));324const Contact = lazy(() => import('./pages/Contact'));325const AdminPanel = lazy(() => import('./pages/AdminPanel'));326327function AppRouter() {328return (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<Route336path="/admin"337element={338<ProtectedRoute>339<AdminPanel />340</ProtectedRoute>341}342/>343</Routes>344</Suspense>345</Router>346);347}348349// Preloading lazy components350const ProductCatalog = lazy(() => import('./ProductCatalog'));351352function ProductButton() {353const [showCatalog, setShowCatalog] = useState(false);354355// Preload on hover356const handleMouseEnter = () => {357import('./ProductCatalog'); // Preload without waiting358};359360return (361<div>362<button363onMouseEnter={handleMouseEnter}364onClick={() => setShowCatalog(true)}365>366View Products367</button>368369{showCatalog && (370<Suspense fallback={<div>Loading catalog...</div>}>371<ProductCatalog />372</Suspense>373)}374</div>375);376}377378// Lazy loading with props379const LazyModalContent = lazy(() => import('./ModalContent'));380381function Modal({ isOpen, contentType, ...props }) {382if (!isOpen) return null;383384return (385<div className="modal">386<Suspense fallback={<div>Loading modal content...</div>}>387<LazyModalContent type={contentType} {...props} />388</Suspense>389</div>390);391}392```393394## Types395396```javascript { .api }397// Higher-order component types398type NamedExoticComponent<P = {}> = ExoticComponent<P> & {399displayName?: string;400};401402type ForwardRefExoticComponent<P> = NamedExoticComponent<P> & {403$$typeof: symbol;404};405406type LazyExoticComponent<T extends ComponentType<any>> = ExoticComponent<ComponentProps<T>> & {407$$typeof: symbol;408};409410// Ref forwarding types411type ForwardedRef<T> = ((instance: T | null) => void) | MutableRefObject<T | null> | null;412413type ForwardRefRenderFunction<T, P = {}> = (414props: P,415ref: ForwardedRef<T>416) => ReactElement | null;417418type PropsWithoutRef<P> = Pick<P, Exclude<keyof P, 'ref'>>;419420// Component prop extraction421type ComponentProps<T extends keyof JSX.IntrinsicElements | JSXElementConstructor<any>> =422T extends JSXElementConstructor<infer P>423? P424: T extends keyof JSX.IntrinsicElements425? JSX.IntrinsicElements[T]426: {};427```