linkifyを使って文字列に含まれているURLをLinkコンポーネントに変換する
今とある個人開発を行っていて、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が上がってました。
Issueのやり取りを見るとバージョン4.1にマイルストーンが設定されているので、もしかしたら4.1で解決するっぽいです。
それまではLinkify用のコンポーネントを作って回避する感じかなと思います。
さいごに
linkifyを使って文字列に含まれているURLをLinkコンポーネントに変換する方法をまとめてみました。
linkifyはURL以外にも、#付き文字列(ハッシュタグ)や@付き文字列(メンション)の変換が出来るようなプラグインもあるので、一通り揃ってる感じがして非常に良いです。
もし同じようなことしたい場合には参考にしてもらえたら幸いです。