- 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
refs.md docs/
1# References & Utilities23React provides utilities for creating and managing references to DOM elements and component instances, along with other helpful utility functions.45## Capabilities67### createRef89Creates a ref object for class components to access DOM elements or component instances.1011```javascript { .api }12/**13* Creates a ref object for class components14* @returns Ref object with current property15*/16function createRef<T = any>(): RefObject<T>;1718interface RefObject<T> {19readonly current: T | null;20}21```2223**Usage Examples:**2425```javascript26import React, { createRef, Component } from 'react';2728// Basic DOM element access in class component29class InputComponent extends Component {30constructor(props) {31super(props);32this.inputRef = createRef();33}3435componentDidMount() {36// Focus input when component mounts37this.inputRef.current.focus();38}3940getValue = () => {41return this.inputRef.current.value;42};4344clear = () => {45this.inputRef.current.value = '';46this.inputRef.current.focus();47};4849render() {50return (51<div>52<input53ref={this.inputRef}54type="text"55placeholder="Enter text"56/>57<button onClick={this.clear}>Clear</button>58</div>59);60}61}6263// Multiple refs in class component64class FormComponent extends Component {65constructor(props) {66super(props);67this.nameRef = createRef();68this.emailRef = createRef();69this.submitButtonRef = createRef();70}7172handleSubmit = (e) => {73e.preventDefault();7475const formData = {76name: this.nameRef.current.value,77email: this.emailRef.current.value78};7980if (!formData.name) {81this.nameRef.current.focus();82return;83}8485if (!formData.email) {86this.emailRef.current.focus();87return;88}8990this.props.onSubmit(formData);91};9293render() {94return (95<form onSubmit={this.handleSubmit}>96<input97ref={this.nameRef}98type="text"99placeholder="Name"100required101/>102103<input104ref={this.emailRef}105type="email"106placeholder="Email"107required108/>109110<button ref={this.submitButtonRef} type="submit">111Submit112</button>113</form>114);115}116}117118// Ref for custom component instance119class CustomButton extends Component {120focus = () => {121this.buttonRef.current.focus();122};123124click = () => {125this.buttonRef.current.click();126};127128constructor(props) {129super(props);130this.buttonRef = createRef();131}132133render() {134return (135<button136ref={this.buttonRef}137className="custom-button"138onClick={this.props.onClick}139>140{this.props.children}141</button>142);143}144}145146class ParentComponent extends Component {147constructor(props) {148super(props);149this.customButtonRef = createRef();150}151152handleFocusButton = () => {153this.customButtonRef.current.focus();154};155156render() {157return (158<div>159<CustomButton ref={this.customButtonRef} onClick={this.handleClick}>160Click Me161</CustomButton>162163<button onClick={this.handleFocusButton}>164Focus Custom Button165</button>166</div>167);168}169}170```171172### Ref Callbacks173174Alternative to ref objects using callback functions for more control over ref assignment.175176```javascript { .api }177/**178* Callback ref function type179* @param instance - DOM element or component instance180*/181type RefCallback<T> = (instance: T | null) => void;182```183184**Usage Examples:**185186```javascript187import React, { Component } from 'react';188189// Callback ref with storage190class CallbackRefExample extends Component {191constructor(props) {192super(props);193this.inputElement = null;194}195196setInputRef = (element) => {197this.inputElement = element;198199if (element) {200console.log('Input ref attached:', element);201// Perform setup when ref is attached202element.addEventListener('keydown', this.handleKeyDown);203} else {204console.log('Input ref detached');205// Cleanup when ref is detached206if (this.inputElement) {207this.inputElement.removeEventListener('keydown', this.handleKeyDown);208}209}210};211212handleKeyDown = (event) => {213if (event.key === 'Enter') {214this.handleSubmit();215}216};217218handleSubmit = () => {219if (this.inputElement) {220console.log('Input value:', this.inputElement.value);221}222};223224render() {225return (226<div>227<input228ref={this.setInputRef}229type="text"230placeholder="Press Enter to submit"231/>232</div>233);234}235}236237// Conditional ref assignment238class ConditionalRefExample extends Component {239constructor(props) {240super(props);241this.state = { shouldFocusOnMount: true };242}243244setInputRef = (element) => {245if (element && this.state.shouldFocusOnMount) {246element.focus();247this.setState({ shouldFocusOnMount: false });248}249};250251render() {252return (253<input254ref={this.setInputRef}255type="text"256placeholder="Auto-focused on mount"257/>258);259}260}261262// Dynamic ref management263class DynamicRefsExample extends Component {264constructor(props) {265super(props);266this.itemRefs = new Map();267}268269setItemRef = (id) => (element) => {270if (element) {271this.itemRefs.set(id, element);272} else {273this.itemRefs.delete(id);274}275};276277scrollToItem = (id) => {278const element = this.itemRefs.get(id);279if (element) {280element.scrollIntoView({ behavior: 'smooth' });281}282};283284render() {285const { items } = this.props;286287return (288<div>289<div className="item-list">290{items.map(item => (291<div292key={item.id}293ref={this.setItemRef(item.id)}294className="item"295>296{item.content}297</div>298))}299</div>300301<div className="controls">302{items.map(item => (303<button304key={item.id}305onClick={() => this.scrollToItem(item.id)}306>307Go to {item.id}308</button>309))}310</div>311</div>312);313}314}315```316317### useId318319Generates stable unique IDs that are consistent between server and client rendering.320321```javascript { .api }322/**323* Generates stable unique IDs for accessibility324* @returns Unique ID string325*/326function useId(): string;327```328329**Usage Examples:**330331```javascript332import React, { useId } from 'react';333334// Form field with accessibility335function FormField({ label, type = 'text', required, error, ...props }) {336const id = useId();337const errorId = `${id}-error`;338const helpId = `${id}-help`;339340return (341<div className="form-field">342<label htmlFor={id}>343{label}344{required && <span className="required" aria-label="required">*</span>}345</label>346347<input348id={id}349type={type}350aria-describedby={error ? errorId : (props.help ? helpId : undefined)}351aria-invalid={!!error}352{...props}353/>354355{props.help && (356<div id={helpId} className="help-text">357{props.help}358</div>359)}360361{error && (362<div id={errorId} className="error-message" role="alert">363{error}364</div>365)}366</div>367);368}369370// Usage371function LoginForm() {372return (373<form>374<FormField375label="Email"376type="email"377required378help="We'll never share your email"379/>380381<FormField382label="Password"383type="password"384required385error="Password must be at least 8 characters"386/>387</form>388);389}390391// Multiple IDs in one component392function TabPanel({ tabs, activeTab, onTabChange }) {393const tablistId = useId();394const panelIdPrefix = useId();395396return (397<div>398<div role="tablist" id={tablistId} aria-label="Content tabs">399{tabs.map((tab, index) => (400<button401key={index}402role="tab"403id={`${tablistId}-tab-${index}`}404aria-controls={`${panelIdPrefix}-panel-${index}`}405aria-selected={index === activeTab}406onClick={() => onTabChange(index)}407>408{tab.title}409</button>410))}411</div>412413{tabs.map((tab, index) => (414<div415key={index}416role="tabpanel"417id={`${panelIdPrefix}-panel-${index}`}418aria-labelledby={`${tablistId}-tab-${index}`}419hidden={index !== activeTab}420>421{tab.content}422</div>423))}424</div>425);426}427428// Dialog with proper ARIA relationships429function Dialog({ isOpen, title, children, onClose }) {430const dialogId = useId();431const titleId = useId();432const descriptionId = useId();433434if (!isOpen) return null;435436return (437<div438role="dialog"439id={dialogId}440aria-labelledby={titleId}441aria-describedby={descriptionId}442aria-modal="true"443>444<h2 id={titleId}>{title}</h2>445446<div id={descriptionId}>447{children}448</div>449450<button onClick={onClose} aria-label="Close dialog">451×452</button>453</div>454);455}456457// Nested component IDs458function NestedForm() {459const formId = useId();460461return (462<form id={formId}>463<fieldset>464<legend>Personal Information</legend>465<FormField label="First Name" required />466<FormField label="Last Name" required />467</fieldset>468469<fieldset>470<legend>Contact Information</legend>471<FormField label="Email" type="email" required />472<FormField label="Phone" type="tel" />473</fieldset>474</form>475);476}477```478479### act (Testing Utility)480481Ensures that updates related to "units" of interaction with a user interface have been processed and applied to the DOM before making assertions.482483```javascript { .api }484/**485* Wraps test interactions to ensure proper effect flushing486* @param callback - Function containing component interactions487* @returns Promise that resolves when effects are flushed488*/489function act(callback: () => void | Promise<void>): Promise<void>;490```491492**Usage Examples:**493494```javascript495import React, { useState, useEffect } from 'react';496import { act, render, fireEvent } from '@testing-library/react';497498// Component for testing499function Counter() {500const [count, setCount] = useState(0);501const [message, setMessage] = useState('');502503useEffect(() => {504setMessage(`Count is ${count}`);505}, [count]);506507return (508<div>509<p data-testid="count">{count}</p>510<p data-testid="message">{message}</p>511<button onClick={() => setCount(count + 1)}>Increment</button>512</div>513);514}515516// Test with act517test('counter updates correctly', async () => {518const { getByTestId, getByRole } = render(<Counter />);519const button = getByRole('button', { name: 'Increment' });520521// Wrap interactions in act522await act(async () => {523fireEvent.click(button);524});525526expect(getByTestId('count')).toHaveTextContent('1');527expect(getByTestId('message')).toHaveTextContent('Count is 1');528});529530// Component with async effects531function AsyncDataComponent({ userId }) {532const [user, setUser] = useState(null);533const [loading, setLoading] = useState(true);534535useEffect(() => {536let cancelled = false;537538fetchUser(userId).then(userData => {539if (!cancelled) {540setUser(userData);541setLoading(false);542}543});544545return () => {546cancelled = true;547};548}, [userId]);549550if (loading) return <div>Loading...</div>;551552return <div>User: {user.name}</div>;553}554555// Test async effects with act556test('loads user data', async () => {557const mockUser = { id: 1, name: 'John Doe' };558jest.spyOn(global, 'fetchUser').mockResolvedValue(mockUser);559560const { getByText } = render(<AsyncDataComponent userId={1} />);561562expect(getByText('Loading...')).toBeInTheDocument();563564// Wait for async effects to complete565await act(async () => {566await new Promise(resolve => setTimeout(resolve, 0));567});568569expect(getByText('User: John Doe')).toBeInTheDocument();570});571572// Testing state updates573test('handles multiple state updates', async () => {574function MultiStateComponent() {575const [count, setCount] = useState(0);576const [doubled, setDoubled] = useState(0);577578const handleClick = () => {579setCount(c => c + 1);580setDoubled(c => (c + 1) * 2);581};582583return (584<div>585<span data-testid="count">{count}</span>586<span data-testid="doubled">{doubled}</span>587<button onClick={handleClick}>Update</button>588</div>589);590}591592const { getByTestId, getByRole } = render(<MultiStateComponent />);593594await act(async () => {595fireEvent.click(getByRole('button'));596});597598expect(getByTestId('count')).toHaveTextContent('1');599expect(getByTestId('doubled')).toHaveTextContent('2');600});601```602603### version604605The React library version string.606607```javascript { .api }608/**609* React library version string610*/611const version: string;612```613614**Usage Examples:**615616```javascript617import React from 'react';618619// Access version from React object620console.log(`React version: ${React.version}`);621622// Or import directly623import { version } from 'react';624console.log(`React version: ${version}`);625626// Feature detection based on version627function checkReactVersion() {628const [major, minor] = version.split('.').map(Number);629630if (major >= 18) {631console.log('React 18+ features available');632return 'concurrent';633} else if (major >= 17) {634console.log('React 17+ features available');635return 'modern';636} else {637console.log('Older React version');638return 'legacy';639}640}641642// Runtime version logging643function logReactInfo() {644console.log({645reactVersion: version,646buildMode: process.env.NODE_ENV,647timestamp: new Date().toISOString()648});649}650```651652## Types653654```javascript { .api }655// Ref types656interface RefObject<T> {657readonly current: T | null;658}659660interface MutableRefObject<T> {661current: T;662}663664type Ref<T> = RefCallback<T> | RefObject<T> | null;665type RefCallback<T> = (instance: T | null) => void;666type LegacyRef<T> = string | Ref<T>;667668// Create ref function669function createRef<T = any>(): RefObject<T>;670671// Hook for IDs672function useId(): string;673674// Testing utility675function act(callback: () => void | Promise<void>): Promise<void>;676677// Version678const version: string;679```