Next.jsでChakraUIを使用しようとしたら useSystemColorMode のエラーが出たので解決
問題
上記の公式ドキュメントを元に Next.js で ChakraUI を使用しようとして、プロバイダーのセットアップを行った後に npm run dev
したら以下のエラーがでたので解決した。
TypeError: Cannot read properties of undefined (reading 'useSystemColorMode')
解決策
冒頭で紹介した Next.js で Chakara UI を使用するための 公式ドキュメントでは以下の項目が記載されていたが、"Provider Setup" の項目だけ行ってしまうと冒頭の TypeErorr が出力されてしまう。
- Provider Setup
- Customizing theme
- Color Mode Script
- Notes on TypeScript
- ChakraProvider Props
結論としては、"Customizing theme" の項目を行うとエラーがでなくなる。最初読んだ限りでは、この項目はオプショナルのものかと思ったが、実はやらないとエラーがでるものだった。
また、Chakra UI の公式ドキュメントだけでなく、Next.js が提供している以下のサンプルプロジェクトをみると理解するのに役立った。
このサンプルプロジェクトは以下のコマンドでローカルインストールできる。
npx create-next-app --example with-chakra-ui with-chakra-ui-app
# or
yarn create next-app --example with-chakra-ui with-chakra-ui-app
準備(プロジェクトの作成)
プロジェクトの作成は以下の通り。
Create Next App を npx で使用してプロジェクトに Next.js のテンプレートを展開する。テスト用として最小限にしたいので Next.js のチュートリアルで使用する example のテンプレートプロジェクトを使用する。
npx create-next-app nextjs-blog --use-npm --example "https://github.com/vercel/next-learn/tree/master/basics/learn-starter"
公式ドキュメントの Installation に従って Chakra UI をインストールする。
npm i @chakra-ui/react @emotion/react@^11 @emotion/styled@^11 framer-motion@^6
これによって、各ライブラリのインストールバージョンは以下となる。
"dependencies": {
"@chakra-ui/react": "^1.8.6",
"@emotion/react": "^11.8.2",
"@emotion/styled": "^11.8.1",
"framer-motion": "^6.2.8",
"next": "latest",
"react": "17.0.2",
"react-dom": "17.0.2"
}
ディレクトリ構造については、src/pages/
ではなく Next.js のチュートリアル通りのものとして、テンプレートプロジェクトとして展開された pages/
を使用する。
Provider setup
公式ドキュメントの "Provider Setup" の項目通りに、次のコードを pages/_app.js
ファイルに追加。ChakraProvider
で Component
をラップして Chakra UI を使用できるように準備する。
import { ChakraProvider } from '@chakra-ui/react'
function MyApp({ Component, pageProps }) {
return (
<ChakraProvider>
<Component {...pageProps} />
</ChakraProvider>
)
}
export default MyApp
ただし、この状態で npm run dev
すると冒頭の TypeErorr が起きる。
Customizing theme
Provider Setup した状態のコードから最小限でエラーがでないように pages/_app.js
ファイルに "Customizing theme" の項目に記載されているコードを少し修正して追記する。
import { ChakraProvider } from '@chakra-ui/react'
+// 1. extendTheme 関数をインポート
+import { extendTheme } from '@chakra-ui/react';
+// 2. custom colorやfontなどで theme を拡張する
+const colors = {
+ brand: {
+ 900: '#1a365d',
+ 800: '#153e75',
+ 700: '#2a69ac',
+ },
+}
+const theme = extendTheme({ colors })
+// 3. ChakraProvider に `theme` prop を渡す
function MyApp({ Component, pageProps }) {
return (
- <ChakraProvider>
+ <ChakraProvider theme={theme}>
<Component {...pageProps} />
</ChakraProvider>
)
}
export default MyApp
これで、npm run dev
をおこなっても冒頭の TypeError が出力されずにコンパイルを正しく実行できるようになった。
$ npm run dev
# エラーが出力されないようになった
Next.js で Chakra UI を使用しようとすると、このようにテーマのカスタマイズを行う必要があるようだ。ChakarUI ではデフォルトテーマとカスタマイズスタイリングを深く統合できる extendThems
という関数を提供しており、この関数の返り値を theme
に代入し、それを ChakraProvider
の prop として渡してあげる必要がある。
extendTheme
関数について具体的な使い方については次の公式ドキュメントに記載されている。
実は Chakra UI の公式ドキュメントだと、App
と MyApp
というように表記がゆれており、そもそもどこのファイルに記載すればよいのかわかりづらくなっていた。
App
と MyApp
については、次の Next.js のドキュメントを参照。
pages/_app.js
や src/pages/_app.js
ファイルにおいて、各ページの初期化を行っている App
コンポーネントを上書きできる。App
コンポーネントを _app.js
で上書きすることで主に以下のことができる。
- ページが変化する間もレイアウトを保持させる
- ページ遷移時に stata を保持する
-
componentDidCatch
を使用してエラーハンドリングをカスタマイズする - page に追加のデータを挿入する
- グローバル CSS を追加する
チュートリアルやこのドキュメントでは、App
なのか MyApp
なのか表記ゆれしているが名前はなんでも良いように思われる(テストしてみたが名前は何でも良かった)。
Next.js のドキュメントについて、そこで紹介されている次のコードを見る限りでは getInitialProps
メソッドを使用しない限りは、App
を import する必要はないので、もし import するなら上書きするコンポーネントの名前は App
ではなく MyApp
などの名前にする必要がある、という話だと思われる。
// import App from 'next/app'
function MyApp({ Component, pageProps }) {
return <Component {...pageProps} />
}
// Only uncomment this method if you have blocking data requirements for
// every single page in your application. This disables the ability to
// perform automatic static optimization, causing every page in your app to
// be server-side rendered.
//
// MyApp.getInitialProps = async (appContext) => {
// // calls page's `getInitialProps` and fills `appProps.pageProps`
// const appProps = await App.getInitialProps(appContext);
//
// return { ...appProps }
// }
export default MyApp
Next.js が提供しているサンプルプロジェクト with-chakra-ui-app
では、const theme = extendTheme({ colors })
で使用した theme
を pages/_app.js
ファイルに記載せず、theme.js
というテーマ専用に分離したファイルへ記載して export default
していた。実際に開発でテーマ設定のカスタマイズを行うさいには、そのように分離する方が良いだろうと思われる。
ちなみに、サンプルプロジェクトのディレクトリ構造は次のようになっていた。
.
├── node_modules/
├── package-lock.json
├── package.json
├── README.md
└── src/
├── components/
│ ├── Container.js
│ ├── CTA.js
│ ├── DarkModeSwitch.js
│ ├── Footer.js
│ ├── Hero.js
│ └── Main.js
├── pages/
│ ├── _app.js
│ ├── _document.js
│ └── index.js
└── theme.js
このサンプルプロジェクトでは、theme.js
で export default
した theme
を _app.js
ファイルにて import theme from '../theme'
でインポートしているので、冒頭のエラーが起きないようになっている。
src/pages
ディレクトリと pages
ディレクトリの違いについては公式ドキュメントの以下のページを参照。
ちなみに src
を使用したディレクトリでのプロジェクト構造についてよさそうなものが以下の記事で記載されていた。
Color Mode Script
"Customizing theme" の項目についてはわかったが、公式ドキュメントの次の項目 "Color Mode Script" では、このサンプルプロジェクトのように、theme
を分離して import する形をとっていたのでこれについて行おうとすると混乱の原因になっていた。
冒頭の TypeError についての解決は終わっているが、ローカルストレージとの正しい同期を機能させるためにやらないといけないという旨がかかれていたので、一応コードを調整してから追加する。
まず、pages
ディレクトリに _document.js
ファイルを作成し、公式ドキュメント通りのコードを追記。
// pages/_document.js
import { ColorModeScript } from '@chakra-ui/react'
import NextDocument, { Html, Head, Main, NextScript } from 'next/document'
import theme from './theme'
export default class Document extends NextDocument {
render() {
return (
<Html lang='en'>
<Head />
<body>
{/* 👇 Here's the script */}
<ColorModeScript initialColorMode={theme.config.initialColorMode} />
<Main />
<NextScript />
</body>
</Html>
)
}
}
Document
と pages/_document.js
ファイルの詳細については次のドキュメントを参照。
ただし、このままだと theme
は pages/_app.js
で宣言してあるので、Next.js のサンプルプロジェクトのように theme
にまつわる部分を theme.js
としてファイルで分離させる必要がある。従って、pages/_app.js
を以下のように変更する。
import { ChakraProvider } from '@chakra-ui/react';
+import theme from '../styles/theme';
-// 1. extendTheme 関数をインポート
-import { extendTheme } from '@chakra-ui/react';
-// 2. custom colorやfontなどで theme を拡張する
-const colors = {
- brand: {
- 900: '#1a365d',
- 800: '#153e75',
- 700: '#2a69ac',
- },
-}
-const theme = extendTheme({ colors })
// 3. ChakraProvider に `theme` prop を渡す
function MyApp({ Component, pageProps }) {
return (
<ChakraProvider theme={theme}>
<Component {...pageProps} />
</ChakraProvider>
);
}
export default MyApp
同様に pages/_document.js
での import 先を変更する。
// pages/_document.js
import { ColorModeScript } from '@chakra-ui/react'
import NextDocument, { Html, Head, Main, NextScript } from 'next/document'
-import theme from './theme'
+import theme from '../styles/theme'
export default class Document extends NextDocument {
render() {
return (
<Html lang='en'>
<Head />
<body>
{/* 👇 Here's the script */}
<ColorModeScript initialColorMode={theme.config.initialColorMode} />
<Main />
<NextScript />
</body>
</Html>
)
}
}
ちなみにディレクトリ構造は Next.js のチュートリアル通りに以下のようなものとする(テンプレートプロジェクト通りの最小限構成)。
.
├── node_modules/
├── package-lock.json
├── package.json
├── README.md
├── pages/
│ ├── _app.js
│ ├── _document.js
│ └── index.js
├── public/
└── styles/
└── theme.js
styles
ディレクトリに theme.js
ファイルを作成し、"Customizing theme" の項目で追加したコードを追加する。更に、外部で使用出来るように export default
を行う。
import { extendTheme } from '@chakra-ui/react'
const colors = {
brand: {
900: '#1a365d',
800: '#153e75',
700: '#2a69ac',
},
}
const theme = extendTheme({ colors })
export default theme
先程、pages/_document.js
にて initialColorMode={theme.config.initialColorMode}
というコードを記載した。公式ドキュメントでは、デフォルトで light
ということになっているが、一応明記しておき、さらに冒頭の TypeError にでてきた useSystemColorMode
の設定について記載しておく。
冒頭のエラーの内容を思い出すと、そもそも「未定義のプロパティを読み込めないよ」という内容だった。
TypeError: Cannot read properties of undefined (reading 'useSystemColorMode')
従って、MyApp
に prop として渡すことのできる theme
さえあれば、デフォルト値 false
がよみこまれてエラーが回避できる仕様になっているようだ。今回は明示的に true
にする(実は、<ChakraProvider theme={theme}>
としなくてもただインポートするだけでローカルのエラーは回避できた)。
再び styles/theme.js
を以下のように変更する。
import { extendTheme } from '@chakra-ui/react'
const colors = {
brand: {
900: '#1a365d',
800: '#153e75',
700: '#2a69ac',
},
}
+const config = {
+ useSystemColorMode: true,
+ initialColorMode: 'light',
+}
-const theme = extendTheme({ colors })
+const theme = extendTheme({
+ colors,
+ config,
+})
export default theme
これで OS の preference に合わせて light/dark のテーマが変わるようになった。
ChakraProvider Props
pages/_app.js
ファイルで使用する ChakraProvider
に渡せる props について "ChakraProvider Props" の項目でリスト化されていた。
-
resetCSS
: 自動的に<CSSRest />
を含ませる- 型:
boolean
- デフォルト値:
true
- 型:
-
theme
: オプショナルなカスタムテーマを使用する- 型:
Theme
- デフォルト値:
@chakra-ui/theme
- 型:
-
colorModeManager
: コンポーネント内でユーザーのカラーモード設定を永続化するマネージャー- 型:
StrongManager
- デフォルト値:
localStorageManager
- 型:
-
portalZIndex
: Portal 用の共通の z-index- 型:
number
- デフォルト値:
undefined
- 型:
theme
にデフォルト値があるらしいので theme
単体で使おうとしたらやはりエラーがでた。import theme from '@chakra-ui/theme'
してもダメだったので今までのやり方をやはり行う必要がある。
Issue
検索したらこのエラーについての issue を見つけた。
Discussion