概要
react-responsive
というライブラリをNext.jsで使おうとすると、
React Hydration Errorが出てきて、永遠にバグる、、、というエラーに遭遇しました。
なかなか解決できない中、GithubのIssueで素晴らしい海外ニキが
仕様まで読み込んで解決していたので、備忘録として記事にさせていただきました🙇
あくまでChakura UI
、react-responsive
で実装されている
useMediaQuery
での解説で、似通っているところがあるのでは?と考えた
筆者の独断と偏見が大いにありますので
この記事は仕様を確認するきっかけ程度に留めましょう。
useMediaQueryの仕様
Chakura UIのGithubでの注意書き
この API は、ユーザーのブラウザによる window.matchMedia のサポートに依存しており、サポートされていない場合や存在しない場合は常に false を返すことに留意してください (サーバーサイドレンダリングの場合など)。
といった記述があったそうで、コードは以下になります。
function useMediaQuery(…) {
if (typeof window === "undefined") {
throw new Error("useMediaQuery() can not be used outside of the browser");
}
…
}
これを見てみると、HookがSSRの間に使用できることを示していて、サーバーで使わない場合、
バグを発生させるのではなく、アプリをクラッシュさせる仕様です。
え、、、???
そもそもuseMediaQueryがSSRでしか動かない実装??
わんちゃんreact-responsive
もそういう感じっぽいですが、一応確認してみます。
実際にGithubを覗いてみる
ということで、GithubのレポジトリへGo!
const useMediaQuery = (settings: MediaQuerySettings, device?: MediaQueryMatchers, onChange?: (_: boolean) => void) => {
const deviceSettings = useDevice(device)
const query = useQuery(settings)
if (!query) throw new Error('Invalid or missing MediaQuery!')
// 長いので省略
return matches
}
似たような実装をされてる雰囲気、、、😇
ということは、ChakuraUIのuseMediaQuery
同様に
海外ニキの手法を取ることが正解っぽいですね、、、🤔
(具体的な仕様、動作する実装方法がわかる方いたら教えていただきたいです🙏)
対処法
海外ニキが出した答えは、カスタムフックを作ることでした!
先ほどの実装内にあるバグを削除したものを
カスタムフックとして作成することで動作させているようです🙌
import { useEffect, useState } from 'react'
const useMediaQuery = (mediaQueryString: string) => {
const [matches, setMatches] = useState<boolean>(false)
useEffect(() => {
const mediaQueryList = window.matchMedia(mediaQueryString)
const listener = () => setMatches(!!mediaQueryList.matches)
listener()
mediaQueryList.addListener(listener)
return () => mediaQueryList.removeListener(listener)
}, [mediaQueryString])
return matches
}
export default useMediaQuery
このフックを使うことで、Hydration Errorが消えて動作しました!
addListener
, removeListener
は非推奨です。
続く内容で訂正しているので、そちらを参照してください。
訂正
addlistener
, removelistner
は非推奨ですが、上記の方法では使用していました。
こちらをaddEventListener
, removeEventListener
で書き換えると
import { useEffect, useState } from 'react'
const useMediaQuery = (mediaQueryString: string) => {
const [matches, setMatches] = useState<boolean>(false)
useEffect(() => {
const mediaQueryList = window.matchMedia(mediaQueryString)
const listener = () => setMatches(!!mediaQueryList.matches)
listener()
mediaQueryList.addEventListener('change', listener)
return () => mediaQueryList.removeEventListener('change', listener)
}, [mediaQueryString])
return matches
}
export default useMediaQuery
上記のようになります🙇
MDNを見ても非推奨になっている(=今後動作しなくなる可能性がある)ので、
IDEでdeprecated
が見えた時には使わないようにしましょう!
参考
終わりに
今回は、Githubの中まで少し覗く結果になりました。
たまーに仕様を見て、エラーの原因を探ったり、
今回のように既存の海外ニキの解決法を見る→覗いて原因を探ったり
といったことがあります。
みなさんもどうしてもあってるはずなのにつまずいた時は
Deeplの拡張機能で翻訳してもいいので、Githubを覗いてみてください🔥
それでは👋
参考文献