Why not login to Qiita and try out its useful features?

We'll deliver articles that match you.

You can read useful information later.

7
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

ReactのuseRefを使う上での注意点

Posted at

はじめに

DOMのバウンディングボックスなどを取得する場合、hooksuseRefを使いたいケースがあります。しかし、下位のコンポーネントにそれらの情報を渡す場合などは、注意が必要になります。

次のようにメッセージの右下にいいねボタンを表示したいとします。
スクリーンショット 2020-05-23 10.21.52.png

うまくいかないパターン

ThumbsUpコンポーネントに対してボタン位置の基準となるHTMLElementを渡し、ThumbsUpにてボタンの表示位置を求めています。

App.tsx
import React, { useRef } from "react";
import ThumbsUp from "./ThumbsUp";
import "./styles.scss";

export default function App() {
  const ref = useRef<HTMLSpanElement | null>();
  return (
    <div>
      <span ref={ref} className="baloon">
        こんにちは
      </span>
      {ref.current && (
        <ThumbsUp anchor={ref.current} />
      )}
    </div>
  );
}

ThumbsUp.tsx
import React, { useRef, useEffect } from "react";
import "./styles.scss";

type Props = {
  anchor: HTMLElement;
};
export default function Portal({ anchor }: Props) {
  const ref = useRef<HTMLDivElement | null>(null);
  useEffect(() => {
    if (ref.current) {
      const anchorRect = anchor.getBoundingClientRect();
      const position = {
        top: anchorRect.top + anchorRect.height - 20,
        left: anchorRect.left + anchorRect.width - 10
      };
      ref.current.style.top = `${position.top}px`;
      ref.current.style.left = `${position.left}px`;
    }
  }, [anchor]);
  return (
    <div ref={ref} className="thumbs-up">
      <button>
        <span role="img" aria-label="thumbs-up">
          👍
        </span>
      </button>
    </div>
  );
}

何が起こるか

残念ながら、意図通りにいいねボタンが表示されません。
スクリーンショット 2020-05-23 10.19.35.png
これは公式にも記載されていますが、App.tsxにおいて、ref.currentが置き換わっても再レンダーは発生しないためです。そのため、ThumbsUpはレンダリングされないままの状態となっています。

解決策

コールバック式のrefuseStateを使うことで解決することができます。下のようにspanrefに関数を与えることで、setAnchorが呼ばれたタイミングで再レンダーを発生させ、意図通りThumbsUpをレンダリングできるようになります。

App.tsx
import React, { useState } from "react";
import ThumbsUp from "./ThumbsUp";
import "./styles.scss";

export default function App() {
  const [anchor, setAnchor] = useState<HTMLSpanElement>();
  return (
    <div>
      <span
        ref={elm => { // コールバック関数を与える
          setAnchor(elm);
        }}
        className="baloon"
      >
        こんにちは
      </span>
      {anchor && <ThumbsUp anchor={anchor} /> }
    </div>
  );
}
7
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?