2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

useMediaQuery React Hydration Error【備忘録】

Last updated at Posted at 2022-12-30

概要

react-responsiveというライブラリをNext.jsで使おうとすると、
React Hydration Errorが出てきて、永遠にバグる、、、というエラーに遭遇しました。
なかなか解決できない中、GithubのIssueで素晴らしい海外ニキが
仕様まで読み込んで解決していたので、備忘録として記事にさせていただきました🙇

あくまでChakura UIreact-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!

useMediaQuery.ts
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同様に
海外ニキの手法を取ることが正解っぽいですね、、、🤔
(具体的な仕様、動作する実装方法がわかる方いたら教えていただきたいです🙏)

対処法

海外ニキが出した答えは、カスタムフックを作ることでした!
先ほどの実装内にあるバグを削除したものを
カスタムフックとして作成することで動作させているようです🙌

hooks/useMediaQuery.ts
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で書き換えると

useMediaQuery.ts
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を覗いてみてください🔥
それでは👋

参考文献

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?