linkifyを使って文字列に含まれているURLをLinkコンポーネントに変換する

linkifyを使って文字列に含まれているURLをLinkコンポーネントに変換する
Photo by Chris Leipelt / Unsplash

今とある個人開発を行っていて、APIで取得した文字列含まれているURLをLinkコンポーネントに変換する必要が出てきました。

一番かんたんな実装は、String.prototype.replaceを使う方法だと思うんですが、これだとdangerouslySetInnerHTMLを使うことになるので、出来れば他の方法が良いなと思っていたところに、linkifyを教えてもらいました。ありがとうございます!

ということで、linkifyを使って実装しようとしたんですが、思ったような感じでは実装できなかったので、どうやって実装したのかを書いていきたいと思います。

手順

①必要なNPMパッケージをインストールする

$ yarn add linkify linkify-react

Next.jsを使っているので、linkify-reactも利用します。

ちなみに、react-linkifyというパッケージもありますが、これは別物なのでご注意を。
執筆時のこちらは5年ほどメンテナンスされていませんでした。

②Linkコンポーネントを作る

import {
  Link as ChakraLink,
  LinkProps as ChakraLinkProps,
} from '@chakra-ui/react'
import NextLink, { LinkProps as NextLinkProps } from 'next/link'

export type LinkProps = {
  href: string
  children: React.ReactNode
  nextLinkProps?: NextLinkProps
  chakraUiLinkProps?: ChakraLinkProps
}

export const Link = ({
  href,
  children,
  nextLinkProps,
  chakraUiLinkProps,
}: LinkProps) => {
  return (
    <NextLink href={href} passHref {...nextLinkProps}>
      <ChakraLink {...chakraUiLinkProps}>{children}</ChakraLink>
    </NextLink>
  )
}

Next.jsとChakra UIを使っているので、それをいい感じに使うために、オジリナルのLinkコンポーネントを作っています。

③linkify用のLinkコンポーネントを作る

import { Link, LinkProps } from '@/components/Link'

type LinkifyLinkProps = LinkProps

export const LinkifyLink = (props: LinkifyLinkProps) => {
  return (
    <Link href={props.href} chakraUiLinkProps={{ isExternal: true }} {...props}>
      {props.children}
    </Link>
  )
}

これを作らないといけない経緯については、後ほど解説します。

④react-linkifyを使ったコンポーネントを作る

import React from 'react'
import LinkifyReact from 'linkify-react'
import { LinkifyLink } from '@/components/LinkifyLink'

type SimpleFormatProps = { children: React.ReactNode }

export const SimpleFormat = ({ children }: SimpleFormatProps) => {
  return (
    <LinkifyReact
      options={{
        tagName: {
          url: () => LinkifyLink, // 文字列にあるURLをLinkifyLinkに変換する
        },
        nl2br: true, // 改行をbrタグに変換する
      }}
    >
      {children}
    </LinkifyReact>
  )
}

解説

linkify用のLinkコンポーネントを作った理由

本当は以下のような感じで、LinkifyLinkコンポーネントを作らずに、Linkコンポーネントを使って、その場でPropsを指定したいんですが、2022年8月時点ではできないみたいです。

import React from 'react'
import LinkifyReact from 'linkify-react'

import { Link } from '@/components/Link'

type FormatProps = { children: React.ReactNode }

export const Format = ({ children }: FormatProps) => {
  return (
    <LinkifyReact
      options={{
        tagName: {
          url: ({ href, children }) => {
            return {
              <Link href={href} chakraUiLinkProps={{ isExternal: true }}>
                {children}
              </Link>
            }
          }
        },
        nl2br: true,
      }}
    >
      {children}
    </LinkifyReact>
  )
}

以下のようにエラーが出てうまくいきません。

Error: Element type is invalid: expected a string (for built-in components) or a class

同じようにしたい人はいるらしく、Issueが上がってました。

React Router Linkify component · Issue #169 · Hypercontext/linkifyjs
I have a case for mention and hashtags to be linked internally to the app. I am using react router for handling internal links. Currently linkifyjs/react implementation will return tagElement retur…

Issueのやり取りを見るとバージョン4.1にマイルストーンが設定されているので、もしかしたら4.1で解決するっぽいです。

それまではLinkify用のコンポーネントを作って回避する感じかなと思います。

さいごに

linkifyを使って文字列に含まれているURLをLinkコンポーネントに変換する方法をまとめてみました。

linkifyはURL以外にも、#付き文字列(ハッシュタグ)や@付き文字列(メンション)の変換が出来るようなプラグインもあるので、一通り揃ってる感じがして非常に良いです。

もし同じようなことしたい場合には参考にしてもらえたら幸いです。