react: [Basic] useRef in useEffect Object is possibly undefined

What cheatsheet is this about? (if applicable)

How to use typescript and useRef

What’s your issue or idea?

import React, { useEffect, useRef } from 'react'
export default function() {
  const myRef = useRef()
  useEffect(() => {
    myRef.current.classList.add('something')
    // Object is possibly 'undefined'.ts(2532)
  })
  return (
    <div ref={myRef}></div>
  )
}

The following “works” / “typescript doesn’t complain”… but is that the proper way?

import React, { useEffect, useRef } from 'react'

export default function() {
  const myRef = useRef(null)
  useEffect(() => {
    const node = myRef.current as any
    node.classList.add('something')
  })
  return (
    <div ref={myRef}></div>
  )
}

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Reactions: 13
  • Comments: 17

Most upvoted comments

Typescript seems to like this one

import React, { useEffect, useRef } from 'react'

export default function() {
  const myRef = useRef<HTMLDivElement>(null)
  useEffect(() => {
    const node = myRef.current
    node?.classList.add('something')
    // ^ note the new ? similar to `if (node)`
  })
  return (
    <div ref={myRef}></div>
  )
}

why not use like this “useRef<any>()” it works fine btw

useRef is a generic so you can pass any type you want into it so that it can internally know what .current is supposed to be. Since you’re using null as the initial value, I think you need to add that to your type you’re declaring as well. So useRef<HTMLDivElement>(null) should be useRef<HTMLDivElement | null>(null) so typescript knows your myRef.current property can be HTMLDivElement or null.

You don’t always have to declare the type your ref will be if you know the type won’t change. For example, if you do something like

const myRef = useRef(false);
if (!myRef.current) {
    myRef.current = true
}

typescript will infer it as a boolean:

image

@hellatan there are overloads for useRef such that if you use a type param T vs T | null the resultant object is different:

// @type = { readonly current: HTMLDivElement | null }
const refObjectToPassIntoRefProp = useRef<HTMLDivElement>(null)
// @type = { current: HTMLDivElement | null }
const refObjectYouMaintain = useRef<HTMLDivElement | null>(null)
// @type = { current: HTMLDivElement | undefined }
const refObjectYouMaintainToo = useRef<HTMLDivElement>()

With the first, the current property of the object is read-only to you, the consumer. It’s meant to be passed into React as the pointer is maintained by React. In practice, you can still mutate the current property of something you pass into React but it’s not recommended as React is supposed to be the owner.

If you’re using a ref object to pass into a ref prop for an element, you need to use the | null overload, otherwise it will complain to you. The third form is for cases where you maintain the pointer yourself (instead of React).

Good to know, it makes sense now. I was looking for readonly (but didn’t know it exists) so your first example useRef<HTMLDivElement>(null) is exactly what I want.

The 3rd example errors for me

// @type = { current: HTMLDivElement | undefined }
const refObjectYouMaintainToo = useRef<HTMLDivElement>()

Error

Type 'MutableRefObject<HTMLDivElement | undefined>' is not assignable to type 'string | ((instance: HTMLDivElement | null) => void) | RefObject<HTMLDivElement> | null | undefined'.
  Type 'MutableRefObject<HTMLDivElement | undefined>' is not assignable to type 'RefObject<HTMLDivElement>'.
    Types of property 'current' are incompatible.
      Type 'HTMLDivElement | undefined' is not assignable to type 'HTMLDivElement | null'.
        Type 'undefined' is not assignable to type 'HTMLDivElement | null'.  TS2322

code snippet (top component no conditional render)

  const myRef = useRef<HTMLDivElement>()
  return (
    <div className="App">
      <div ref={myRef}></div>

Maybe because I use CRA, which is a little behind @types/react current 6.9.19, latest 16.9.21

@ferdaber so do you mean when you don’t pass in T | null, typescript will complain if you try to do myRef.current = 'some new ref'?