Question
I am using React with TypeScript and want to use refs with static typing and IntelliSense for referenced DOM nodes. In this class component, how should I type and create a ref for the <input> element so I can safely access it?
import * as React from "react";
interface AppState {
count: number;
}
interface AppProps {
steps: number;
}
interface AppRefs {
stepInput: HTMLInputElement;
}
export default class TestApp extends React.Component<AppProps, AppState> {
constructor(props: AppProps) {
super(props);
this.state = {
count: 0,
};
}
incrementCounter() {
this.setState({ count: this.state.count + 1 });
}
render() {
return (
<div>
<h1>Hello World</h1>
<input type="text" ref="stepInput" />
<button onClick={() => this.incrementCounter()}>Increment</button>
Count: {this.state.count}
</div>
);
}
}
Short Answer
React refs provide a way to access a DOM element or component instance directly. You will learn how to create typed refs in TypeScript, safely handle their possible null value, and use them in both class and function components.
Concept
A ref is a React object that points to something rendered by React, commonly a DOM element such as an input, button, video, or canvas.
Most React code should update the UI through state and props. However, some browser actions are inherently imperative: focusing an input, reading a media element's playback state, scrolling an element, or connecting a third-party library. Refs are the appropriate tool for those jobs.
In TypeScript, the generic type tells React what the ref will point to:
React.createRef<HTMLInputElement>()
This gives ref.current the type HTMLInputElement | null. TypeScript can then offer autocomplete for input methods and properties such as .focus(), .value, and .select().
The null is important. Before React renders the element, and after it unmounts, there may be no DOM node assigned to the ref. Your code must account for that possibility.
The ref="stepInput" syntax in the original code is a legacy string ref. Avoid it in modern React because it is less explicit and does not provide the same clean, type-safe ref object workflow.
Mental Model
Think of React as a construction manager building a page from a blueprint. Usually, you change the blueprint by updating state, and React rebuilds the necessary parts.
A ref is like a labeled direct contact for one finished object on the construction site. For example, a ref labeled stepInput lets you tell the actual input element, “focus yourself now.”
The contact may not exist yet while the page is still being built, so current can be null. Check that the contact exists before calling it.
Syntax and Examples
Create a class field with React.createRef, provide the DOM element type, and pass that object to the element's ref prop.
import * as React from "react";
interface AppProps {
steps: number;
}
interface AppState {
count: number;
}
export default class TestApp extends React.Component<AppProps, AppState> {
private stepInputRef = React.createRef<HTMLInputElement>();
state: AppState = {
count: 0,
};
focusStepInput = () => {
this.stepInputRef.current?.focus();
};
incrementCounter = () => {
this.setState((previousState) => ({
count: previousState.count + 1,
}));
};
render() {
return (
<div>
<input ref={this.stepInputRef} type="text" />
<button onClick={this.focusStepInput}>Focus input</button>
<button onClick={this.incrementCounter}>Increment</button>
<p>Count: {this.state.count}</p>
</div>
);
}
}
React.createRef<HTMLInputElement>() means the ref will hold an input DOM node. The expression this.stepInputRef.current?.focus() uses optional chaining:
- If
currentcontains an input, callfocus(). - If it is
null, do nothing.
For function components, use useRef instead:
import { useRef } from "react";
export function SearchBox() {
const inputRef = useRef<HTMLInputElement>(null);
function focusSearch() {
inputRef.current?.focus();
}
return (
<>
<input ref={inputRef} type="search" />
<button onClick={focusSearch}>Focus search</button>
</>
);
}
Step by Step Execution
Consider this function component:
function NameForm() {
const nameInputRef = React.useRef<HTMLInputElement>(null);
function selectName() {
nameInputRef.current?.select();
}
return (
<div>
<input ref={nameInputRef} defaultValue="Ada" />
<button onClick={selectName}>Select name</button>
</div>
);
}
useRef<HTMLInputElement>(null)creates a stable ref object whose initialcurrentvalue isnull.- React renders the
<input>and receivesnameInputRefthroughref={nameInputRef}. - After the input is attached to the page, React assigns the actual
HTMLInputElementtonameInputRef.current. - The user clicks Select name.
selectNameruns and evaluatesnameInputRef.current?.select().- Because the input exists, React calls the browser's
HTMLInputElement.select()method, highlighting the text. - If the input were conditionally removed,
currentcould becomenull; optional chaining prevents a runtime error.
Real World Use Cases
Refs are useful when a direct browser or component action is needed:
- Autofocus after an action: Focus a search field when a dialog opens.
- Form usability: Select invalid text or focus the first invalid input after validation.
- Media controls: Call
play(),pause(), or readcurrentTimefrom anHTMLVideoElement. - Scrolling: Call
scrollIntoView()on a newly added message or validation error. - Canvas and charts: Obtain an
HTMLCanvasElementso a charting or drawing library can use it. - Third-party widgets: Give a date picker, map, or editor library a specific DOM container.
Example: scrolling an error message into view.
const errorRef = React.useRef<HTMLParagraphElement>(null);
function showError() {
errorRef.current?.scrollIntoView({ behavior: "smooth" });
}
return <p ref={errorRef}>Please enter a valid email address.</p>;
Real Codebase Usage
In production React applications, refs are usually kept small and purpose-specific.
Use event handlers for imperative actions
A common pattern is to focus only after a user action:
const emailRef = React.useRef<HTMLInputElement>(null);
function handleEditEmail() {
emailRef.current?.focus();
}
Use guard clauses when the element must exist
If continuing without the element would be a programming error, explicitly return early:
function copyValue() {
const input = emailRef.current;
if (!input) {
return;
}
navigator.clipboard.writeText(input.value);
}
Keep form data in state when it affects rendering
Do not use a ref as a replacement for state when the displayed UI depends on a value. Use controlled inputs for live validation, previews, disabled buttons, and submitted data.
const [email, setEmail] = React.useState("");
<input
value={email}
onChange={(event) => setEmail(event.target.value)}
/>
Forward refs through reusable components
A design-system input may forward its ref so parent components can focus the real input:
import { forwardRef } from "react";
type TextInputProps = React.ComponentPropsWithoutRef<"input">;
const TextInput = forwardRef<HTMLInputElement, TextInputProps>(
function TextInput(props, ref) {
return <input {...props} ref={ref} />;
}
);
This preserves the HTMLInputElement type for consumers.
Common Mistakes
Using legacy string refs
// Avoid
<input ref="stepInput" />
String refs are legacy API. Use a ref object instead:
const inputRef = React.useRef<HTMLInputElement>(null);
<input ref={inputRef} />
Forgetting that current can be null
// TypeScript correctly reports that current may be null.
inputRef.current.focus();
Use optional chaining or a guard clause:
inputRef.current?.focus();
Assigning the wrong DOM type
// Incorrect: a textarea is not an HTMLInputElement.
const fieldRef = React.useRef<HTMLInputElement>(null);
<textarea ref={fieldRef} />;
Match the element type:
const fieldRef = React.useRef<HTMLTextAreaElement>(null);
Creating a ref during every render
// Avoid in function components.
function Search() {
const inputRef = React.createRef<HTMLInputElement>();
return <input ref={inputRef} />;
}
createRef() makes a new object each time this function renders. Use useRef, which keeps the same object between renders:
function Search() {
const inputRef = React.useRef<HTMLInputElement>(null);
return <input ref={inputRef} />;
}
Using refs for data that should trigger a rerender
Changing ref.current does not rerender the component. Use state for visible values, such as a counter or input text.
Comparisons
| Approach | Best use | Rerenders when changed? | Example |
|---|---|---|---|
useState | Data that affects displayed UI | Yes | Current count, input value, loading state |
useRef | DOM access or mutable data that should not rerender | No | Input node, timer ID, previous value |
createRef | Refs in class components | No | private inputRef = React.createRef<HTMLInputElement>() |
| Callback ref | Run custom logic when a node is attached or detached | Not by itself | Store or measure a DOM node |
| String ref | Legacy syntax; avoid in new code | No | ref="input" |
useRef and createRef solve the same basic problem, but they belong in different component styles:
- Use
React.createRef<T>()as a field in a class component. - Use
useRef<T>(initialValue)inside a function component.
A callback ref is useful when assigning the node should immediately perform custom work:
const setInputNode = (node: HTMLInputElement | null) => {
if (node) {
node.focus();
}
};
<input ref={setInputNode} />;
Cheat Sheet
// Class component
private inputRef = React.createRef<HTMLInputElement>();
<input ref={this.inputRef} />
this.inputRef.current?.focus();
// Function component
const inputRef = React.useRef<HTMLInputElement>(null);
<input ref={inputRef} />
inputRef.current?.focus();
| DOM element | Ref type |
|---|---|
<input> | HTMLInputElement |
<textarea> | HTMLTextAreaElement |
<button> | HTMLButtonElement |
<div> | HTMLDivElement |
<form> | HTMLFormElement |
<video> | HTMLVideoElement |
<canvas> | HTMLCanvasElement |
Rules:
currentmay benull; use?.or a null check.- Use
ref={myRef}, not a string ref. - Use state for values that must update the rendered UI.
- In function components, create DOM refs with
useRef<T>(null). - In class components, create DOM refs with
createRef<T>()as a class field.
FAQ
How do I type an input ref in React TypeScript?
Use React.useRef<HTMLInputElement>(null) in a function component, or React.createRef<HTMLInputElement>() in a class component.
Why is ref.current possibly null in TypeScript?
The element may not have rendered yet, or it may have been removed from the page. Check it with optional chaining (ref.current?.focus()) or an if statement.
Should I use a ref to read an input value?
You can for simple uncontrolled forms, using inputRef.current?.value. Use state when the value affects rendering, validation feedback, or other UI behavior while the user types.
Can I use useRef in a class component?
No. Hooks such as useRef are for function components. Use React.createRef in class components.
Are string refs supported in React TypeScript?
They are legacy syntax and should not be used in new code. Ref objects and callback refs are clearer and work better with TypeScript.
How do I pass a ref to my own React component?
A regular component does not automatically pass refs to its inner DOM element. Use forwardRef to explicitly forward the ref.
Does changing ref.current rerender React?
No. A ref stores mutable data without triggering a render. Use state if the changed value must appear in the UI.
Mini Project
Description
Build a small class-based React form that uses a typed input ref to focus a required field and select its contents. This mirrors a common form-validation interaction: guide the user directly to the field that needs attention.
Goal
Create a typed HTMLInputElement ref and use it to focus and select a name field when the user submits an empty form.
Requirements
Create a class component with a name input and a submit button.
Create the input ref with React.createRef<HTMLInputElement>().
Store the current name and validation message in component state.
On submission, show an error when the name is empty and focus the input.
Select the input text when a non-empty name is submitted.
Keep learning
Related questions
@Directive vs @Component in Angular: Differences, Use Cases, and When to Use Each
Learn the difference between @Directive and @Component in Angular, including use cases, examples, and when to choose each.
Accessing Input Value from EventTarget in TypeScript
Learn why EventTarget has no value property in TypeScript and safely read values from HTML input events in Angular applications.
Angular (change) vs (ngModelChange): What’s the Difference?
Learn the difference between Angular (change) and (ngModelChange), when each fires, and which one to use in forms and inputs.