Callback Ref を useCallback の外で参照する


こんにちは、Gaji-Labo アシスタントエンジニアの石垣です。

今回は Callback Ref を useCallback の外で参照する方法についてまとめたいと思います。

やりたいこと

DOM要素を useEffect 等で操作したい時、 useEffect 内で useRef を使った参照をすると、参照したい要素が初期にレンダリングされていない際に参照できないという弱点があります。

モーダルの高さを開いた時に取得したいが出来ない
const [scrollHeight, setScrollHeight] = useState(0);
const ref = useRef<HTMLDivElement>();
useEffect(() => {
  if (ref.current) {
    setScrollHeight(ref.current.scrollHeight);
  }
}, [ref.current]);

例えば上記の画像のように、最初はレンダリングされていないモーダルの縦幅を取得したい時などは useRef では対応することが出来ません。

その場合は Callback Ref を使用することで要素がレンダリングされているかどうかに関わらず要素の状態を取得・操作することができます。

しかし、Callback Ref を使用すると ref の要素は useCallback の関数内で node として割り当てられるので、外の関数で使用することが出来ません。

これを解決するには、 useCallback の中で ref.current に node を渡します。

解決方法

まずは useRef を作成し、その後 useCallback の中で ref.current に node を渡すことで Callback Ref を useEffect 内で参照することが出来るようになります。

const ref = useRef<HTMLDivElement>();
const setRef = useCallback((node: HTMLDivElement) => {
  ref.current = node;
}, []);

コンポーネントには setRef を渡します。
他の関数の内では通常の ref オブジェクトと同じように ref.current でDOMノードを取得することが出来ます。

function Sandbox() {
  const [items, setItems] = useState<string[]>([]);
  const [open, setOpen] = useState(false);
  const [scrollWidth, setScrollHeight] = useState(0);
  const ref = useRef<HTMLDivElement>();
  const setRef = useCallback((node: HTMLDivElement) => {
    if (node) {
      setScrollWidth(node.scrollHeight);
    }
    ref.current = node;
  }, []);
  const handleClickButton = () => {
    if (ref.current) {
      setItems([...items, "item"]);
      setScrollWidth(ref.current.scrollHeight);
    }
  };
  return (
    <div>
      <Button
        variant="contained"
        onClick={() => {
          setOpen(true);
        }}
      >
        Open Modal
      </Button>
      <Modal
        open={open}
        onClose={() => {
          setOpen(false);
        }}
      >
        <Box sx={style}>
          <div ref={setRef}>
            <Button variant="contained" onClick={handleClickButton}>
              Add Item
            </Button>
            <Typography variant="h6" component="h2">
              scrollWidth: {scrollWidth}
            </Typography>
            {items.map((item, index) => (
              <Typography variant="h6" component="h2">
                {item}
                {index}
              </Typography>
            ))}
          </div>
        </Box>
      </Modal>
    </div>
  );
}
モーダルを開いた時とアイテムを追加した際に高さが取得できている

まとめ

今回は Callback Ref を useEffect 内で参照する方法についてまとめました。

最初はレンダリングされていない要素を操作するということで、使い所は少なくないかなと思います。

React コンポーネントを実装する際に参考にしていただければ幸いです。

Gaji-Laboでは、React経験が豊富なフロントエンドエンジニアを募集しています

弊社ではReactの知見で事業作りに貢献したいフロントエンドエンジニアを募集しています。大きな制作会社や事業会社とはひと味もふた味も違うGaji-Laboを味わいに来ませんか?

もちろん、一緒にお仕事をしてくださるパートナーさんも随時募集中です。まずはお気軽に声をかけてください。お仕事お問い合わせや採用への応募、共に大歓迎です!

求人応募してみる!

投稿者 Ishigaki Shotaro

未経験から Gaji-Labo に入社。現在は React/TypeScript/Next.js の案件で MUI を使ったコンポーネント組み込みを担当。プロジェクトチームのリードとして共に組み込み作業をしているメンバーの進行管理も行っています。休日はだいたい家で音楽を聴いており、たまにライブに出かけています。