React (TypeScript) で debounce な処理を作っていて timer を useRef
で作った ref オブジェクトに格納しようとしたらエラーになった
環境
- React
18.2.0
- TypeScript
4.9.5
ref.current
への代入で型エラー Cannot assign to 'current' because it is a read-only property.
import { useRef, useEffect } from 'react'; function MyComponent() { const timerRef = useRef<ReturnType<typeof setTimeout>>(null); // … 略 useEffect(() => { timerRef.current = setTimeout(callback, timeout); // => Type Error return () => clearTimeout(timerRef.current); }, []); }
timerRef.current =
の代入の箇所で下記のような型エラーになった
Cannot assign to 'current' because it is a read-only property.ts(2540). (property) RefObject<NodeJS.Timeout>.current: any
null
型が含まれることを明記しせず null
で初期化した ref オブジェクトは読み取り専用になる
In TypeScript,
useRef
returns a reference that is either read-only or mutable, depends on whether your type argument fully covers the initial value or not.
To access a DOM element: provide only the element type as argument, and usenull
as initial value. In this case, the returned reference will have a read-only.current
that is managed by React.
cf. GitHub - typescript-cheatsheets/react: Cheatsheets for experienced React developers getting started with TypeScript
The issue is that when we pass
null
as an initial value to theuseRef
hook and don't includenull
in the type we passed to the hook's generic, we create an immutable ref object.
cf. (useRef) Cannot assign to 'current' because it is read-only property | bobbyhadz
解決方法: useRef
の型に null
を含める
ジェネリックでの型指定を null
を含めた Union 型にすると ref.current
に代入可能になる
上記の setTimeout のタイマーに利用したい例は下記のように変更すればOK
import { useRef, useEffect } from 'react'; function MyComponent() { - const timerRef = useRef<ReturnType<typeof setTimeout>>(null); + const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null); // … 略 useEffect(() => { timerRef.current = setTimeout(callback, timeout); // => OK return () => clearTimeout(timerRef.current); }, []); }
Note: HTMLElement への ref
The same would be the case if your ref points to a DOM element.
You'd have to type the hook asconst ref = useRef<HTMLElement | null>(null)
if you need to change the value of the ref'scurrent
property.
Note that you wouldn't have to include null in the ref's type if you aren't directly assigning to itscurrent
property.
import { useRef } from 'react'; const App = () => { const ref = useRef<HTMLInputElement>(null); return <input ref={ref} type="text" defaultValue="" />; };
The
ref
in the example is used to focus the input element.
There is no assignment to the ref's.current
property, so it's not necessary to includenull
in its type.
cf. (useRef) Cannot assign to 'current' because it is read-only property | bobbyhadz
input タグなど ref=
で ref オブジェクトを指定する使い方の場合 .current
への代入ではないので型定義に null
を含めなくても問題がないとのこと
.current
に代入する ref オブジェクトは useRef<some type | null>(null)
と型定義に null
を含めて初期化する必要がある!
おわり
[参考]
- (useRef) Cannot assign to 'current' because it is read-only property | bobbyhadz
- GitHub - typescript-cheatsheets/react: Cheatsheets for experienced React developers getting started with TypeScript
- React + TypeScript: useRefの3つの型指定と初期値の使い方 - Qiita
- reactjs - type for useRef if used with setInterval, react-typescript - Stack Overflow
- フック API リファレンス – React