Mutable and immutable useRef semantics with React & TypeScript
Tags: typescript, react, webdev, hooks
Published: 2021-06-12 04:03:28

---
title: Mutable and immutable useRef semantics with React & TypeScript
published: true
description: Learn how different ways of instantiating useRef influence the ref mutability.
tags: typescript, react, webdev, hooks
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y0ll392gqwm6i77k6lty.jpeg
---
In this post, you will learn how different ways declaring a ref with `useRef` hook influence the immutability of the `current` ref property. We will be looking at how to make the `current` property immutable, mutable, and know without much effort if the ref is one or the other.

**All the behavior I'm going to talk about is only relevant in the context of TypeScript. The mutability / immutability is enforced at type level, not runtime level**.

## Immutable `current` property

The immutable semantics of the `useRef` hooks are usually used with DOM elements. A common use-case might be to get the ref of an element and focus that element whenever a button is clicked.

Here is how I would write that.

```tsx
import * as React from "react";

const Component = () => {
  const inputRef = React.useRef<HTMLInputElement>(null);

  return (
    <div>
      <input type="text" name="name" ref={inputRef} />
      <button type="button" onClick={() => inputRef.current?.focus()}>
        Click to focus the input
      </button>
    </div>
  );
};
```

Notice the type and the value I’ve initialized the `useRef` with. The semantics I’ve used signal that I’m relying on React to manage the ref for me. In our case, this means that I cannot mutate the `inputRef.current`. If I ever tried to do that, TypeScript would complain.

```tsx
import * as React from "react";

const Component = () => {
  const inputRef = React.useRef<HTMLInputElement>(null);


  return (
    <div>
        {/* Cannot assign to 'current' because it is a read-only property */}
      <input type = "text" ref = {callbackRefValue => inputRef.current = callbackRefValue}>
      <button type="button" onClick={() => inputRef.current?.focus()}>
        Click to focus the input
      </button>
    </div>
  );
};
```

After writing similar code for a while, I’ve created a rule of thumb I follow to understand if the ref that I’m looking is immutable.

> If the `useRef` is initialized with `null` and the initial value **does not** belong to the provided type, the `current` property is immutable.

In our case, the `null` initial value does not belong to the type `HTMLInputElement` so the `current` property cannot be mutated.

## Mutable `current` property

To have the `current` property of the ref be mutable, we need to change how we are declaring ref itself.

Suppose we are writing a component that deals with timers. The `useRef` hook is an ideal candidate to hold a reference to a timer. With the timer reference at hand, we can make sure that we clear the timer when the component unmounts.

Here is an, albeit a bit contrived, example.

```tsx
import * as React from "react";

const Component = () => {
  const timerRef = React.useRef<number | null>(null);
  // This is also a valid declaration
  // const timerRef = React.useRef<number>()

    React.useEffect(() => {
        // Mutation of the `current` property
        timerRef.current = setTimeout(/* ... */)
        return clearInterval(timerRef.current)
    }, [])

  return (
      // ...
  );
};
```

Since in the beginning, I have no way to know what the reference to the later declared `setTimeout` might be, I've initialized the `useRef` with `null`. Apart from the types, the declaration of the ref might seem eerily similar to the one in the _Immutable `current` property_ section.
However, since the initially provided value (in our case `null`) wholly belongs to the type I've declared the `useRef` with (`number | null`), the `current` property is allowed to be mutable.

Similarly to the immutable `current` property case, here is my rule of thumb.

> If the `useRef` is initialized with a value that belongs to the provided type, the `current` property of the ref is mutable.

In our case, the `null` initial value belongs to the type `number | null` so the `current` property can be mutated.
As an alternative, I could have declared the `timerRef` variable the following way

```tsx
const timerRef = React.useRef<number>(); // the `timerRef.current` is also mutable
```

Why is the `current` allowed to be mutated in this case? Because the `timerRef` is implicitly initialized with the `undefined` value. The `undefined` value belongs to the type I've declared the `timerRef` - the `React.useRef` typings are overloaded depending on the type of the initial value.

```tsx
const timerRef = React.useRef<number>();

// Really is
const timerRef = React.useRef<number>(undefined);

// The `React.useRef` type definitions specify an overload whenever the type of the initial value is `undefined`
function useRef<T = undefined>(): MutableRefObject<T | undefined>; // Notice the `MutableRefObject`.
```

## Summary

When I started working with React & TypeScript, I found the difference between mutable and immutable refs quite confusing. I hope that this article was helpful and cleared some of the questions you might have had on the subject matter.

You can find me on twitter - [@wm_matuszewski](https://twitter.com/wm_matuszewski).

Thank you for your time.