Poznajemy przykład dość zaawansowanego używania useRef, forwardRef oraz useImperativeHandle. Upewniamy się, że wszystko rozumiemy. Do dzieła.

Ok, będziemy mieli:

  • Komponent najniższego rzędu, forwardujący ref od rodzica do HTMLElement
  • Rodzeństwo najniższego rzędu, również forwardujące ref od rodzica, ale nie bezpośrednio do swojego elementu, tylko do useImperativeHandle
  • Komponent pośredni, który:
    • Przyjmuje ref od rodzica
    • Forwarduje ten ref do useImperativeHandle
    • Sam tworzy dwa refy, które wskazują nie na HTMLElement, ale jego dzieci RFC
    • Używa tych dwóch refów wewnątrz useImperativeHandle
  • Komponent-rodzic, który tworzy jakiś ref i przekazuje go do komponentu pośredniego

Ok, jak to wygląda. Pierwszy z komponentów najniżej:

const AddComment = forwardRef(function AddComment(props, ref) {
  return <input placeholder="Add comment..." ref={ref} />;
});

export default AddComment;

Jak widać coś przyjmuje, jakiś ref utworzony wyżej i wskazujący poprzez atrybut ref na RFC AddComponent. Przyjmuje i forwarduje bezpośrednio do swojego HMTLInputElement.

Teraz jego rodzeństwo:


const CommentList = forwardRef(function CommentList(props, ref) {
  const divRef = useRef(null);

  useImperativeHandle(ref, () => {
    return {
      scrollToBottom() {
        const node = divRef.current;
        node.scrollTop = node.scrollHeight;
      }
    };
  }, []);

  let comments = [];
  for (let i = 0; i < 50; i++) {
    comments.push(<p key={i}>Comment #{i}</p>);
  }

  return (
    <div className="CommentList" ref={divRef}>
      {comments}
    </div>
  );
});

export default CommentList;

Jak widać ten komponent również jest najniższego rzędu, ponieważ ref wskazuje na HTMLDivElement a nie jakiś komponent RFC. Ale wskazuje jego własny divRef, zaś ten przyjmowany od swojego rodzica, komponentu pośredniego, jest używany do useImperativeHandle.

Tam, w useImperativeHandle dokonuje pewnych akcji swoim prywatnym refem na HTMLDivElement pokazującym i zwraca publicznie tylko jedną metodę. Komponent pośredni, komponent rodzic będzie mógł mu zrobić ref, ale ten ref będzie mógł tylko metodę scrollToBottm na jego divie wykonać.

Ok, komponent pośredni:

import { forwardRef, useRef, useImperativeHandle } from 'react';
import CommentList from './CommentList.js';
import AddComment from './AddComment.js';

const Post = forwardRef((props, ref) => {
  const commentsRef = useRef(null);
  const addCommentRef = useRef(null);

  useImperativeHandle(ref, () => {
    return {
      scrollAndFocusAddComment() {
        commentsRef.current.scrollToBottom();
        addCommentRef.current.focus();
      }
    };
  }, []);

  return (
    <>
      <article>
        <p>Welcome to my blog!</p>
      </article>
      <CommentList ref={commentsRef} />
      <AddComment ref={addCommentRef} />
    </>
  );
});

export default Post;

Ten komponent:

  • Jest komponentem pośrednim, ponieważ ma refy wskazujące na RFC, które forwardują te refy dalej (do HTMLElementu, albo useImperativeHandle, które pracuje na HTMLElemencie swoim prywatnym refem)
  • Przyjmuje ref od rodzica i używa w useImperativeHandle, rodzic może zrobić mu refa, ale będzie mógł korzystać tylko z metod, które on mu zapewnia
  • Tworzy dwa refy, które są do komponentów RFC, które je forwardują niżej, jeden komponent bezpośrednio do HTMLElement, drugi pośrednio do useImperativeHandle, które tylko metodę scrollToBottom publicznie udostępnia
  • Wewnątrz własnego useImperativeHandle, komponent pośredni korzysta ze swoich refów i tego, co mu te komponenty niżej forwardują, rodzic komponentu pośredniego będzie mógł mu ustawić ref i tylko te metody wywołać

Ok, oto rodzic:

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

export default function Page() {
  const postRef = useRef(null);

  function handleClick() {
    postRef.current.scrollAndFocusAddComment();
  }

  return (
    <>
      <button onClick={handleClick}>
        Write a comment
      </button>
      <Post ref={postRef} />
    </>
  );
}

Przeanalizujmy sobie, jak to wszystko działa, bo nie jest to trudne, ale trzeba to dobrze zrozumieć.