Przypominamy sobie, czym jest hook useRef oraz poznajemy funkcję forwardRef, która pozwala na przekazywanie ref do naszego komponentu. Do dzieła.

Ok, useRef służy dwóm celom:

  • Utworzeniu zmiennej, która przetrwa diff-rendery (re-rendery), jednocześnie jej zmiana nie wywoła kolejnego re-rendera
  • Utworzeniu referencji do DOM elementu w naszym komponencie

Ref jest szczególnie przydatne, gdy chcemy zmienną wewnątrz RFC, która:

  • Nie jest współdzielona przez wszystkie instancje RFC (jak zmienna zdefiniowana powyżej/poniżej RFC w tym samym pliku)
  • Nie jest tworzona od nowa przy każdym re-renderze
  • Jest zbyt prosta w tworzeniu, aby marnować na nią useMemo – jej utworzenie jeden raz wynika z logiki, nie z oszczędzania

I tak tworzymy wtedy useRef w RFC, dajemy null, sprawdzamy, czy ref.current istnieje, jeżeli nie to przypisujemy wartość. I mamy co chcieliśmy.

Oczywiście, manipulowanie elementami DOM w komponencie za pomocą useRef też już znamy. My natomiast chcemy teraz, aby jakiś komponent wyżej mógł manipulować elementem DOM swojego komponentu dziecka.

I od tego jest forwardRef, który:

  • Oplata RFC, lub używając innej terminologii, przyjmuje funkcję render
  • RFC mu przekazane poza pierwszym argumentem, propsami (może być obiekt dekomponujący propsy), przyjmuje drugi argument, jakim jest ref.

W praktyce wygląda to tak:

import { forwardRef } from 'react';

const MyInput = forwardRef(function MyInput(props, ref) {
  const { label, ...otherProps } = props;
  return (
    <label>
      {label}
      <input {...otherProps} ref={ref} />
    </label>
  );
});

export default MyInput;

Logika następująca – komponent rodzic może skorzystać z useRef i dla RFC MyInput przypisać atrybut ref do tamtego ref. I tamto ref będzie forwardowane do ref, które tu ustaliliśmy na input.

Bo chyba rozumiemy, że nasze komponenty, choć wyglądają jak tagi HTML, to nie jest prawdziwy element, do którego można referencję utworzyć. Referencja utworzona przez jakiegokolwiek rodzica MyInput do tego „nieelementu” (ale komponentu) jest przez ten komponent forwardowana do jego elementu input.

Oto, co widzi rodzic-komponent:

import { useRef } from 'react';
import MyInput from './MyInput.js';

export default function Form() {
  const ref = useRef(null);

  function handleClick() {
    ref.current.focus();
  }

  return (
    <form>
      <MyInput label="Enter your name:" ref={ref} />
      <button type="button" onClick={handleClick}>
        Edit
      </button>
    </form>
  );
}

Czyli tak:

  • Rodzic tworzy ref i ustawia atrybut ref na ten ref dla nieelementu MyInput
  • Nieelement MyInput to komponent RFC opleciony przez forwardRef, który posiada instrukcje, aby refy mu przekazane forwardować do swojego elementu input

Ot i cała filozofia.