Question

React Refs in TypeScript: Typing DOM Elements Safely

typescriptreactjs

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.

Take Quiz

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 current contains an input, call focus().
  • 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>
  );
}
  1. useRef<HTMLInputElement>(null) creates a stable ref object whose initial current value is null.
  2. React renders the <input> and receives nameInputRef through ref={nameInputRef}.
  3. After the input is attached to the page, React assigns the actual HTMLInputElement to nameInputRef.current.
  4. The user clicks Select name.
  5. selectName runs and evaluates nameInputRef.current?.select().
  6. Because the input exists, React calls the browser's HTMLInputElement.select() method, highlighting the text.
  7. If the input were conditionally removed, current could become null; 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 read currentTime from an HTMLVideoElement.
  • Scrolling: Call scrollIntoView() on a newly added message or validation error.
  • Canvas and charts: Obtain an HTMLCanvasElement so 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>;
Take Quiz

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

ApproachBest useRerenders when changed?Example
useStateData that affects displayed UIYesCurrent count, input value, loading state
useRefDOM access or mutable data that should not rerenderNoInput node, timer ID, previous value
createRefRefs in class componentsNoprivate inputRef = React.createRef<HTMLInputElement>()
Callback refRun custom logic when a node is attached or detachedNot by itselfStore or measure a DOM node
String refLegacy syntax; avoid in new codeNoref="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} />;
Take Quiz

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 elementRef type
<input>HTMLInputElement
<textarea>HTMLTextAreaElement
<button>HTMLButtonElement
<div>HTMLDivElement
<form>HTMLFormElement
<video>HTMLVideoElement
<canvas>HTMLCanvasElement

Rules:

  • current may be null; 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.

Take Quiz

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.

Take Quiz

Keep learning

Related questions

View more