[Basic] useRef Hook Documentation.

11
closed
rajivpunjabi
rajivpunjabi
Posted 11 months ago

[Basic] useRef Hook Documentation. #388

What cheatsheet is this about? (if applicable)

Basic cheatsheet

What's your issue or idea?

According to the documentation useRef Hook has two options.

const ref1 = useRef<HTMLElement>(null!); 
const ref2 = useRef<HTMLElement | null>(null);

The first option will make ref1.current read-only. But after trying out some examples I feel there are some issues. As it shows MutableRefObject for the first option. So ref1.current is not read-only.

I am exploring TypeScript with React so maybe I am missing something. Can someone please help me with this?

(If applicable) Reproduction of issue in TypeScript Playground

Playground

sw-yx
sw-yx
Created 11 months ago

very nice issue! clearly we are wrong in our useRef notes.

i think @ferdaber might have some insight as to why MutableRefObject is inferred in that example. however based on this I am inclined to change our documented example to the one in your playground const ref2 = React.useRef<HTMLSpanElement>(null);

rajivpunjabi
rajivpunjabi
Created 11 months ago

According to useRef Hook Type Definitions.

 function useRef<T>(initialValue: T): MutableRefObject<T>;
 function useRef<T>(initialValue: T|null): RefObject<T>;
 function useRef<T = undefined>(): MutableRefObject<T | undefined>;

From Playground.

const ref1 = React.useRef<HTMLSpanElement>(null!); //MutableRefObject
const ref2 = React.useRef<HTMLSpanElement>(null); //RefObject
const ref3 = React.useRef<HTMLSpanElement | null>(null); //MutableRefObject

These ref matches the first two type definitions.

  • For ref1 we are defining the initialValue as not null ( non-null assertion operator ) so it matches the first type and returns a MutableRefObject.
  • For ref2 we are defining the initialValue as null so it matches the second type and returns a RefObject.
  • For ref3 we are defining the generic parameter as either of HTMLSpanElement | null and the initialValue is null so it matches the first type and returns a MutableRefObject.
sw-yx
sw-yx
Created 11 months ago

ok so check out this example though: (playground)

export default function MyComponent() {
  const ref1 = React.useRef<HTMLDivElement>(null!); // MutableRefObject
  const ref2 = React.useRef<HTMLDivElement>(null); // RefObject
  React.useEffect(() => {
    console.log(ref1.current.innerHTML);
    // TypeScript won't require null-check e.g. ref1 && ref1.current
    console.log(ref2.current.innerHTML);
    // Type error: Object is possibly 'null'.
  });
  return [
    <div ref={ref1}> etc </div>,
    <div ref={ref2}> etc </div>
  ];
}

the goal is to not require null checks because the ref is always bound to the element in this case. i think this is why i made the original recommendation.

rajivpunjabi
rajivpunjabi
Created 11 months ago

If the goal is not to require null checks then its absolutely correct but stating ref1.current will be read-only is wrong. As it is a MutableRefObject so it is not read-only.

sw-yx
sw-yx
Created 11 months ago

alright :)

sw-yx
sw-yx
Created 11 months ago

i made those tweaks here: https://github.com/typescript-cheatsheets/react/pull/389/files , lmk if any changes but otherwise thank you for the feedback :)

bochen2014
bochen2014
Created 11 months ago

would this be more explanatory?

import * as React from "react";

export default function MyComponent() {
  const ref1 = React.useRef<HTMLDivElement>(null!); // MutableRefObject
  const ref2 = React.useRef<HTMLDivElement>(null); // RefObject
  React.useEffect(() => {
    ref1.current=ref2.current!;
    // ok
    ref2.current=ref1.current;
    //Cannot assign to 'current' because it is a read-only property.(2540)
    console.log(ref1.current.innerHTML);
    // TypeScript won't require null-check e.g. ref1 && ref1.current
    console.log(ref2.current.innerHTML);
    // Type error: Object is possibly 'null'.
  });
  return [
    <div ref={ref1}> etc </div>,
    <div ref={ref2}> etc </div>
  ];
}
sw-yx
sw-yx
Created 11 months ago

@bochen2014 i dont feel strongly about this code sample 🤷🏽

thien-do
thien-do
Created 8 months ago

@sw-yx I think we should not encourage the null check bypassing here. The reason is that check is valid in real life, because the developer may forget to assign the ref to an element in his render, or if the ref-ed element is conditionally rendered:

const Foo = () => {
  const ref1 = useRef<HTMLDivElement>(null); // RefObject
  useEffect(() => {
    console.log(ref1.current); // This will be null
  });
  return <div> etc </div>; // Oops, I forgot to assign the ref
}
const Foo = () => {
  const ref1 = useRef<HTMLDivElement>(null); // RefObject
  useEffect(() => {
    console.log(ref1.current); // Only works on Monday!
  });
  return isMonday ? <div ref={ref1} /> : <div> etc </div>;
}

@sw-yx would you mind if I have a PR to clarify this more?

sw-yx
sw-yx
Created 8 months ago

haha nice example @thien-do. yes PR welcome, but i think giving the 3 examples and letting people decide based on the pros and cons feels right. otherwise i'll just keep getting more and more issues fighting back and forth haha

Previous