# How to Implement a React Ref to Access DOM Elements in TypeScript

> Master React refs in TypeScript. Learn to use the useRef hook for type-safe DOM element access, preventing re-renders and ensuring stable references.

- Repository: [Meta/react](https://github.com/facebook/react)
- Tags: how-to-guide
- Published: 2026-02-21

---

**Use the `useRef` hook with a generic type argument (e.g., `useRef<HTMLInputElement>(null)`) to create a mutable reference that persists across renders without causing re-renders, providing type-safe access to DOM nodes.**

The `useRef` hook is the standard mechanism for accessing DOM elements directly in React function components. When working with TypeScript, properly typing your **react ref** ensures compile-time safety while interacting with native browser APIs. This guide examines the implementation details from the `facebook/react` repository to demonstrate type-safe patterns for DOM manipulation.

## Understanding the useRef Hook Architecture

React’s `useRef` hook creates a mutable object whose `.current` property can hold a reference to a DOM element. According to the source code in [`packages/react/src/ReactHooks.js`](https://github.com/facebook/react/blob/main/packages/react/src/ReactHooks.js), the public `useRef` hook simply forwards to the reconciler’s dispatcher (`dispatcher.useRef(initialValue)`).

The internal implementation resides in [`packages/react-reconciler/src/ReactFiberHooks.js`](https://github.com/facebook/react/blob/main/packages/react-reconciler/src/ReactFiberHooks.js), which contains the `mountRef` and `updateRef` functions. These ensure the ref object is created **once** during the component’s first render, guaranteeing a stable identity (`===`) for the entire component lifecycle.

## Creating a Typed React Ref for DOM Elements

To properly type a ref for a DOM element, pass the element interface as a generic to `useRef` and initialize with `null`.

```typescript
import React, { useRef, useEffect } from 'react';

export function FocusableInput() {
  // Create a ref typed to HTMLInputElement
  const inputRef = useRef<HTMLInputElement>(null);

  // Access the DOM node after mounting
  useEffect(() => {
    // Optional chaining guards against null (e.g., during SSR)
    inputRef.current?.focus();
  }, []);

  // Attach the ref to the JSX element
  return <input type="text" ref={inputRef} placeholder="Auto-focused" />;
}

```

The generic `<HTMLInputElement>` tells TypeScript that `inputRef.current` will be an `HTMLInputElement` (or `null`) once the element attaches. This provides autocomplete and type checking for DOM methods like `.focus()`.

## Accessing DOM Nodes with React Refs in Event Handlers

Refs are not limited to effects; you can access them directly in event handlers for immediate DOM manipulation.

```typescript
import React, { useRef } from 'react';

export function ToggleVisibility() {
  const divRef = useRef<HTMLDivElement>(null);

  const toggle = () => {
    if (divRef.current) {
      const el = divRef.current;
      el.style.display = el.style.display === 'none' ? 'block' : 'none';
    }
  };

  return (
    <>
      <button onClick={toggle}>Toggle Visibility</button>
      <div ref={divRef}>Content that can be hidden</div>
    </>
  );
}

```

This pattern avoids React’s state mechanism for purely presentational changes, preventing unnecessary re-renders while maintaining type safety through the `HTMLDivElement` generic.

## Forwarding React Refs in Custom Components

When building reusable components, use `forwardRef` to pass the ref through to a DOM element. Combine it with `useImperativeHandle` to expose a limited API to parent components.

```typescript
import React, { forwardRef, useImperativeHandle, useRef } from 'react';

type Props = { label: string };

export type FancyInputHandle = {
  focus: () => void;
  clear: () => void;
};

export const FancyInput = forwardRef<FancyInputHandle, Props>((props, ref) => {
  const localRef = useRef<HTMLInputElement>(null);

  useImperativeHandle(ref, () => ({
    focus: () => localRef.current?.focus(),
    clear: () => {
      if (localRef.current) {
        localRef.current.value = '';
      }
    },
  }));

  return <input ref={localRef} placeholder={props.label} />;
});

```

The parent component can then use the ref with full type safety:

```typescript
const parentRef = useRef<FancyInputHandle>(null);
<FancyInput ref={parentRef} label="Name" />;
parentRef.current?.focus(); // TypeScript knows this method exists

```

## Key Implementation Files in the React Source Code

The following files from the `facebook/react` repository define and test the ref behavior described above:

- **[`packages/react/src/ReactHooks.js`](https://github.com/facebook/react/blob/main/packages/react/src/ReactHooks.js)** — Defines the public `useRef` hook that forwards to the reconciler dispatcher.
- **[`packages/react-reconciler/src/ReactFiberHooks.js`](https://github.com/facebook/react/blob/main/packages/react-reconciler/src/ReactFiberHooks.js)** — Contains the internal `mountRef` and `updateRef` implementations that ensure ref stability across renders.
- **[`packages/react-reconciler/src/__tests__/useRef-test.internal.js`](https://github.com/facebook/react/blob/main/packages/react-reconciler/src/__tests__/useRef-test.internal.js)** — Test suite demonstrating usage patterns including `useRef(null)` and `useRef(undefined)`.
- **[`packages/eslint-plugin-react-hooks/__tests__/ReactCompilerRuleTypescript-test.ts`](https://github.com/facebook/react/blob/main/packages/eslint-plugin-react-hooks/__tests__/ReactCompilerRuleTypescript-test.ts)** — Shows TypeScript-typed refs in real-world patterns (`React.useRef<ScrollView>(null)`).

## Summary

- **Create typed refs** using `useRef<HTMLElementType>(null)` to ensure compile-time safety when accessing DOM properties.
- **Attach refs** to JSX elements via the `ref` prop, then interact with `ref.current` inside `useEffect` or event handlers.
- **Guard against null** using optional chaining (`?.`) or explicit checks, since the ref is `null` until the component mounts.
- **Forward refs** using `forwardRef` when building reusable components, and use `useImperativeHandle` to expose specific methods to parent components.
- **Reference the implementation** in [`ReactHooks.js`](https://github.com/facebook/react/blob/main/ReactHooks.js) and [`ReactFiberHooks.js`](https://github.com/facebook/react/blob/main/ReactFiberHooks.js) to understand how React guarantees ref stability across renders.

## Frequently Asked Questions

### Why must I initialize useRef with null in TypeScript?

TypeScript requires the initial value to match the generic type. Since the DOM element does not exist until after the first render, `null` is the only valid initial state. Declaring `useRef<HTMLInputElement>(null)` tells TypeScript that `current` will be `HTMLInputElement | null`, forcing you to handle the null case safely.

### What is the difference between useRef and createRef?

`useRef` is a hook designed for function components; it returns the same mutable object on every render. `createRef` is typically used in class components and creates a new ref object on every render, which breaks identity comparisons. According to the React source in [`ReactHooks.js`](https://github.com/facebook/react/blob/main/ReactHooks.js), `useRef` maintains stability through the fiber architecture, while `createRef` lacks this optimization.

### How do I type a ref for a custom component instead of a DOM element?

Use `React.RefObject<YourComponentType>` or `React.MutableRefObject<YourComponentType>` depending on whether the ref holds a mutable value. When forwarding refs to custom components, type the `forwardRef` generic as `forwardRef<RefType, PropsType>`. If exposing imperative methods, define an interface for the handle and use `useImperativeHandle` as shown in the `FancyInput` example above.

### Is it safe to access ref.current during render?

No. React does not guarantee the DOM node exists until after the component mounts. Accessing `ref.current` during the render phase (before `useEffect` runs) may return `null` or stale values. Always interact with refs inside `useEffect`, `useLayoutEffect`, or event handlers to ensure the DOM node is attached and ready for manipulation.