💭

render hooksパターンを活用したツールチップの実装

2022/08/27に公開

概要

MUIが提供しているツールチップを、render hooksパターンを用いてカスタムフックとして提供したいという内容です。

render hooksパターンについては以下の記事で紹介されているので是非読んでみてください!

https://engineering.linecorp.com/ja/blog/line-securities-frontend-3/

https://zenn.dev/mssknd/articles/1046a44b9d9502

実装したいもの

今回はMUIのツールチップをrender hooksパターンを用いて提供してみます。

By default disabled elements like <button> do not trigger user interactions so a Tooltip will not activate on normal events like hover. To accommodate disabled elements, add a simple wrapper element, such as a span.

(引用元: React Tooltip component - Disabled elements - Material UI)

MUIのツールチップは内部の要素が無効化されている場合は、ホバーなどの動作をしてもツールチップが表示されない仕様があります。解決方法としては内部の要素をspanタグで囲う方法が取り上げられていました。

またツールチップの内部の要素が無効化されている場合のみツールチップを表示させたい場合は、ツールチップの挙動を一部上書きする必要があります。

render hooksパターンを使わない実装例

先ほど記載したツールチップを普通に実装してみます。

このコンポーネントに対してボタンのロジックを追加すると、結構ごちゃごちゃになってしまいます。またツールチップのロジックが呼び出し元に漏れてしまい少しつらくなります。他にもspanタグでボタンが囲う部分に対してはMUIのツールチップの仕様であるというコメントを付けないと意図が分かりにくい状態でもあります。

const TooltipButton = () => {
  const disabled = true
  const [opened, setOpened] = useState<boolean>(false)

  const open = () => setOpened(true)
  const close = () => setOpened(false)
  
  return (
    <Tooltip
        open={!disabled && opened}
        onOpen={open}
        onClose={close}
        title={<Typography>ツールチップを表示するよ</Typography>}
      >
        <span>
	  <Button disabled={disabled}>ボタン</Button>
	</span>
      </Tooltip>
  )
}	

render hooksパターンを使う実装例

普通に実装すると先ほど述べたようにつらい部分が出てきてしまいます。render hooksパターンではこういったロジックであったりコンポーネントの仕様をカスタムフックに閉じ込めることが出来ます。
以下は実装例になります。

useCustomTooltip.tsx
interface RenderToolTipProps {
  disabled: boolean
  message: string
  children: JSX.Element
}

export const useCustomTooltip = () => {
  const [opened, setOpened] = useState<boolean>(false)

  const open = () => setOpened(true)
  const close = () => setOpened(false)

  const renderTooltip = ({
    disabled,
    message,
    children,
  }: RenderToolTipProps) => {
    return (
      <Tooltip
        open={!disabled && opened}
        onOpen={open}
        onClose={close}
        title={<Typography>{message}</Typography>}
      >
        <span>{children}</span>
      </Tooltip>
    )
  }

  return renderTooltip;
}

次に実際にツールチップを呼び出すコンポーネントの実装例です。

render hooksパターンを使わない実装に比べて、ツールチップに関するロジックが存在しない点や、ボタンのロジックを追加してもメンテがしやすそうに見えます(感想)

またMUIのツールチップの仕様を取りやめる場合でも、useCustomTooltipを改修するのみで済むので旨味が大きいのでは?と思っています。

const TooltipButton = () => {
  const disabled = true
  const CustomTooltip = useCustomTooltip()
  
  return (
    <CustomTooltip disabled={!disabled} message={"ツールチップを表示するよ"}>
      <Button disabled={disabled}>ボタン</Button>
    </CustomTooltip>
  )
}

まとめ

本記事では、MUIのツールチップをrender hooksパターンを用いてカスタムフックとして切り出す話をさせていただきました。本記事を通してrender hooksパターンのパワーを少しでも感じていただければ幸いです。

Discussion