useMediaQuery React Hydration Error【備忘録】

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

:::note warn
あくまで`Chakura UI`、`react-responsive`で実装されている
`useMediaQuery`での解説で、似通っているところがあるのでは？と考えた
筆者の独断と偏見が大いにありますので
この記事は仕様を確認するきっかけ程度に留めましょう。
:::

# useMediaQueryの仕様
## Chakura UIのGithubでの注意書き

> この API は、ユーザーのブラウザによる window.matchMedia のサポートに依存しており、サポートされていない場合や存在しない場合は常に false を返すことに留意してください (サーバーサイドレンダリングの場合など)。

といった記述があったそうで、コードは以下になります。

```js
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のレポジトリ](https://github.com/yocontra/react-responsive)へGo！

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

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

```ts: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が消えて動作しました！

:::note alert
`addListener`, `removeListener`は非推奨です。
続く内容で訂正しているので、そちらを参照してください。
:::


### 訂正

`addlistener`, `removelistner`は非推奨ですが、上記の方法では使用していました。
こちらを`addEventListener`, `removeEventListener`で書き換えると

```ts: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`が見えた時には使わないようにしましょう！

参考

https://developer.mozilla.org/en-US/docs/Web/API/MediaQueryList/addListener


# 終わりに
今回は、Githubの中まで少し覗く結果になりました。
たまーに仕様を見て、エラーの原因を探ったり、
今回のように既存の海外ニキの解決法を見る→覗いて原因を探ったり
といったことがあります。
みなさんもどうしてもあってるはずなのにつまずいた時は
Deeplの拡張機能で翻訳してもいいので、Githubを覗いてみてください🔥
それでは👋


# 参考文献

https://github.com/chakra-ui/chakra-ui/issues/3580

https://github.com/yocontra/react-responsive

https://developer.mozilla.org/en-US/docs/Web/API/MediaQueryList/addListener