Refs for storing object references

Refs for storing object references

refs: somewhat similar to state, but with some differences:

  • changing the value of a ref will not trigger a re-rendering
  • the value of refs will never be outdated

Refs for storing object references

first use case:

storing values / object references that don't influence the rendering of a component:

  • timer ids
  • request ids
  • WebSocket connections
  • a PWA install prompt (see section PWA)
  • DOM Elements (see next section "Ref property for accessing DOM elements")

Refs for storing object references

second use case:

storing the most up-to-date version of a state

Refs for storing object references

In function components, refs can be created / accessed via useRef

Values are stored in the reference's .current property

Refs for storing object references

an example of both use cases:

function StopWatch() {
  const [time, setTime] = useState(0);
  const timeRef = useRef(time);
  const intervalId = useRef<number>(null);
  function start() {
    setTime(0);
    timeRef.current = 0;
    intervalId.current = setInterval(() => {
      timeRef.current++;
      setTime(timeRef.current);
    }, 1000);
  }
  function stop() {
    clearInterval(intervalId.current);
  }
  return <div>...</div>;
}

Refs for storing object references

Note: often, there can be implementations that avoid refs:

function StopWatch() {
  const [time, setTime] = useState(0);
  const [running, setRunning] = useState(false);
  useEffect(() => {
    if (running) {
      const intervalId = setInterval(() => {
        setTime((time) => time + 1);
      }, 1000);
      return () => clearInterval(intervalId);
    }
  }, [running]);
  return <div>...</div>;
}

Ref property for accessing DOM elements

Ref property for accessing DOM elements

Just like key, the ref property has a special meaning in JSX - enabling direct access to rendered DOM elements

use cases:

  • managing focus, text selection, or media playback
  • integrating with third-party DOM libraries
  • alternative way of managing inputs (uncontrolled components)

Ref property for accessing DOM elements

Using the ref property together with the useRef hook:

function RefDemo() {
  const myRef = useRef<HTMLInputElement>(null);
  return (
    <input
      ref={myRef}
      onChange={() => {
        console.log(myRef.current.value);
      }}
    />
  );
}

Ref property for accessing DOM elements

managing focus, text selection, or media playback

some changes cannot be expressed declaratively (via state); they require direct access to a DOM element

example: there are properties like .value for changing a value or .className for changing classes, but there is no property for managing focus

Ref property for accessing DOM elements

Managing focus:

function App() {
  const inputEl = useRef<HTMLInputElement>(null);
  useEffect(() => {
    inputEl.current?.focus();
  }, []);
  return <input ref={inputEl} />;
};

Ref property for accessing DOM elements

managing media playback:

// https://codesandbox.io/s/media-playback-x3ci4
function Video() {
  const [playing, setPlaying] = useState(false);
  const videoEl = useRef<HTMLVideoElement>(null);
  useEffect(() => {
    if (playing) {
      videoEl.current?.play();
    } else {
      videoEl.current?.pause();
    }
  }, [playing]);
  return (
    <div>
      <video
        ref={videoEl}
        width="250"
        src={
          'https://interactive-examples.mdn.mozilla.net/' +
          'media/cc0-videos/flower.mp4'
        }
      />
      <button onClick={() => setPlaying(!playing)}>
        {playing ? 'pause' : 'play'}
      </button>
    </div>
  );
}

Ref property for accessing DOM elements

integrating with third-party DOM libraries

Third-party libraries may require a DOM element being passed in

Example: Google Maps takes an element where it will paint the map

Many third-party libraries have wrappers for React where refs are not needed

Ref property for accessing DOM elements

integrating Google Maps via a ref:

function Map() {
  const mapRef = useRef<HTMLElement>();
  useEffect(() => {
    new google.maps.Map(mapRef.current);
  }, []);
  return <div ref={mapRef} style={height: 400} />;
}

Ref property for accessing DOM elements

alternative way of managing inputs

using ref instead of value and onChange can mean less code (but is discouraged by the React documentation)

Refs are used by react-hook-form to make form handling simpler and faster

Ref property for managing inputs

Managing inputs: comparing useState and useRef:

const App = () => {
  const [firstName, setFirstName] = useState('');
  const lastNameInput = useRef<HTMLInputElement>(null);

  return (
    <div>
      <input
        value={firstName}
        onChange={(event) =>
          setFirstName(event.target.value)
        }
      />
      <input ref={lastNameInput} />

      <button
        onClick={() => {
          console.log(firstName);
          console.log(lastNameInput.current.value);
        }}
      >
        log values
      </button>
    </div>
  );
};

Callback refs

As we've seen we can pass a Ref object into the ref property

We can also pass in a callback function which will be called with the element as its parameter (react-hook-form uses this)

<input
  ref={(element) => {
    console.log(element);
    console.log(element.value);
    element.focus();
  }}
/>

Ref property in custom components

Ref property in custom components

example: TextField component in MUI

const containerRef = useRef<HTMLDivElement>(null);
const inputRef = useRef<HTMLInputElement>(null);
<TextField ref={containerRef} inputRef={inputRef} />
  • ref: can be used to access the containing <div>
  • inputRef: can be used to access the <input> element

Ref property in custom components

implementation of a ref whose property name is not "ref":

type Props = {
  inputRef: RefObject<HTMLInputElement>;
  // ...
};

function TextField(props: Props) {
  return (
    <div>
      ...
      <input ref={props.inputRef} />
    </div>
  );
}

Ref property in custom components

implementation of a ref whose property name is "ref":

type Props = {
  // ...
};

const TextField = forwardRef<HTMLDivElement, Props>(
  (props, ref) => {
    return (
      <div ref={ref}>
        ...
        <input />
      </div>
    );
  }
);

Ref property in custom components

combined implementation:

type Props = {
  inputRef: RefObject<HTMLInputElement>;
  // ...
};

const TextField = forwardRef<HTMLDivElement, Props>(
  (props, ref) => {
    return (
      <div ref={ref}>
        ...
        <input ref={props.inputRef} />
      </div>
    );
  }
);