Open6

Chakra UI × Next.js でButtonをLinkとして使う時の注意(isDisabled編

IssuyIssuy

TL;DR

aタグとしてレンダリングされるButtonコンポーネントは、isDisabledを指定してもクリックで飛べちゃうので注意。
hrefを省略すると良いです。
ただしNextLinkと絡めて使う場合は省略できないので、工夫が必要です。

IssuyIssuy

Chakra UI を使う場合、通常ボタンはこういう書き方をしますよね。
そして isDisabled=true にするとボタンが非活性になり、押しても何も起こらないのが期待値です。

      <Button colorScheme="blue">通常のボタン</Button>
      <Button colorScheme="blue" isDisabled>
        通常のボタン
      </Button>

IssuyIssuy

このボタンをリンクとして動作させたい場合、 as="a" のようにするとaタグとしてレンダリングされます。
そして href="/path" とすると指定されたページに遷移します。

このとき isDisabled=true を指定するとどのような挙動になりますか?
押しても何も起こらないのがみなさんの期待値だと思います。

      <Button
        as="a"
        href={externalLink}
        target="_blank"
        rel="noopener noreferrer"
        colorScheme="blue"
        isDisabled
      >
        間違ったリンクボタン
      </Button>

(「間違ったリンクボタン」がネタバレ感ありますが。。。)
見た目はボタンが非活性となりますが、クリックすることでページ遷移が発生します。

ではどのようにすれば良いでしょう?
以下の ExternalLinkButton のコンポーネントのように対応してみます。

const ExternalLinkButton = (props: ComponentProps<typeof Button>) => {
  const { isDisabled, href } = props;
  return (
    <Button
      as="a"
      target="_blank"
      rel="noopener noreferrer"
      {...props}
      href={isDisabled ? undefined : href}
    />
  );
};

hrefを省略することでページ遷移が起こらないようになりました。
これで期待値どおりの挙動になりますね。

※ExternalLinkButton: 外部サイトへのリンク時には後述の next/link を使わないのでこの命名にしています
※hrefの省略についてFYI:https://github.com/chakra-ui/chakra-ui/issues/2255#issuecomment-712927834

IssuyIssuy

Next.jsの next/link には同一サイト内のリンクをprefetchしてくれるなどroutingに関するいくつかの機能があります。
https://nextjs.org/docs/api-reference/next/link

これをChakra UIと併用する場合はNextLink として扱います。
https://chakra-ui.com/docs/components/link#usage-with-nextjs

Buttonコンポーネントを next/link として使用するためには as="NextLink " を指定します。
このときisDisabledを指定するとどのような挙動になるでしょう?

      <Button as={NextLink} href="/" colorScheme="blue" isDisabled>
        間違ったNextLinkボタン
      </Button>

ここでもやはりボタンが非活性となりますが、クリックすることでページ遷移が発生します。
そのためhrefを省略したくなるのですが、NextLinkはhrefが必須パラメータなので省略できません。

ではどのようにすれば良いでしょうか?
以下の NextLinkButton のコンポーネントのように対応してみます。

const NextLinkButton = (props: ComponentProps<typeof Button>) => {
  const { isDisabled } = props;
  if (isDisabled) {
    return <Button as="a" {...props} href={undefined} />;
  }
  return <Button as={NextLink} {...props} />;
};

isDisabledでないときはNextLinkを返しています。
isDisabledのときはNextLinkを使わず、ExternalLinkButtonと同様にhrefを省略して返す様にしました。
こうすることでページ遷移が起こらないようになり、期待通りの挙動になりますね。

IssuyIssuy

いかがだったでしょうか?
もしかするとNextLinkの解決法について、より良い方法があるかもしれません。
もしこの例とは異なる解決法を取られてる方がいれば是非教えて下さい!