Jak używać refy w Reakcie?

Podczas nauki Reacta często natrafiałem na przeszkody w postaci smaczków, których się nie spodziewałem. Jednym z nich były referencje do elementów JSX. Wyjaśniam dlaczego tradycyjne refy nie działają na komponentach funkcyjnych i jak je przekazać do innych komponentów.

Dzięki referencjom (refom) w Reakcie możemy odwołać się do elementów JSX, by coś z nimi zrobić. Problem dla początkującego miłośnika naszego ulubionego frameworka pojawia się, gdy ten chce stworzyć referencję do komponentów funkcyjnych lub do elementu w komponencie-dziecku renderowanym przez komponent-rodzica. To nie zadziała. Przedstawię na to inny sposób.

Niepoprawne użycie referencji

Stwórzmy sobie na szybko aplikację reactową i plik App.js, gdzie skorzystamy z dwóch rodzajów komponentów: klasowego i funkcyjnego:

//App.js

import React from 'react';

const ParentComponent = () => {
  return (
    <>
      <ChildClassComponent />
      <ChildFunctionalComponent />
    </>
  );
};

class ChildClassComponent extends React.Component {     //komponent klasowy
  render() {
    return <a href='https://reactywny.pl'>Reactywny.pl</a>;
  }
}

const ChildFunctionalComponent = () => {                                     //komponent funkcyjny
  return <a href='https://reactywny.pl'>Reactywny.pl</a>;
};

export default ParentComponent;

Komponent ParentComponent generuje oba te komponenty. Spróbujmy przesłać do nich referencję i zobaczmy co się stanie:

import React, { useRef, useEffect } from 'react';      //import hooków, w tym do refów

const ParentComponent = () => {
  const linkRef = useRef(null);

  useEffect(() => {
    console.log(linkRef);
  });

  return (
    <>
      <ChildFunctionalComponent ref={linkRef} />
      <ChildClassComponent ref={linkRef} />
    </>
  );
};

Aby mieć pewność, że komponent na pewno przypisze daną referencję do zmiennej używam hooka useEffect, a w nim sprawdzam co otrzymuję:

zdjęcie tematyczne
Nie ma przeszkód w używaniu refów na komponentach klasowych

Dla komponentu klasowego oczywiście to zadziała, a dla funkcyjnego otrzymamy następujący wynik:

zdjęcie tematyczne
Nie możemy używac refów na komponentach funkcyjnych

Jak widać, nasza wartość zmienna to null, a React sam zaznacza, że jest to niepoprawne i proponuje skorzystać z forwardRef. Służy on do przekierowania referencji do renderowanego komponentu a następnie przesłania zwrotnego wartości w tymże komponencie.

Pobieramy wartości DOMu za pomocą forwardRef

Jeżeli chcemy odwołać się do danego elementu JSX, np. linku intuicyjnie wpadlibyśmy prawdopodobnie na taki pomysł:

const ParentComponent = () => {
  //...

  return <ChildFunctionalComponent ref={linkRef} />;
};

const ChildFunctionalComponent = ({ ref }) => {
  return <a href='https://reactywny.pl' ref={ref}>Reactywny.pl</a>;
};

To niestety nie zadziała - również otrzymamy wartość null. Z pomocą przychodzi nam komponent wyższego rzędu (HOC) forwardRef, którego zastosujemy w następujący sposób:

import React, { useRef, useEffect, forwardRef } from 'react';       //import forwardRef

const ParentComponent = () => {
  const linkRef = useRef(null);                                                                    

  useEffect(() => {
    console.log(linkRef.current.textContent);             //wyświetlanie przekazanej wartości
  });

  return <ChildFunctionalComponent ref={linkRef} />;
};

const ChildFunctionalComponent = forwardRef((props, ref) => {     //zastosowanie forwardRef
  return <a href='https://reactywny.pl' ref={ref}>Reactywny.pl</a>;
});

W konsoli ujrzymy następującą wartość:

zdjęcie tematyczne
Referancja jest poprawnie zapisywana

Udało się więc przesłać referencję do komponentu i teraz możemy ją wykorzystywać. Dodam tylko, że aby skorzystać z forwardRef w powyższy sposób, musimy wysłać wysłać komponentowi propsy, nawet jeśli tych nie zdefiniowaliśmy. Inaczej React wyrzuci nam błąd, czego oczywiście nie chcemy. Jest jeszcze jeden sposób użycia forwardRef, ale warunkiem jest, by dany komponent wyeksportować. Do tego utworzyłem nowy plik:

//ChildFunctionalComponent.js
import React, { forwardRef } from 'react';

const ChildFunctionalComponent = (props, ref) => {
  return (
    <a href='https://reactywny.pl' ref={ref}>
      Reactywny.pl
    </a>
  );
};

export default forwardRef(ChildFunctionalComponent);

Efekt jest zupełnie taki sam jak wcześniej, jednak nasz kod zyskuje nieco na piękności. I to w zasadzie  wszystko o refach w kontekście zastosowania. Nie użyłem tutaj metody React.createRef, ponieważ uważam, że powinniśmy używać hooków i komponentów funkcyjnych zamiast przestarzałych już rozwiązań stworzonych dla komponentów klasowych. I to samo polecam Tobie Drogi Czytelniku.

Źródła

© Damian Kalka 2021
Wszelkie prawa zastrzeżone