Webプログラミング

Material-UIでテーマを切り替えられるようにする

Material UIで複数のテーマをユーザが切り替えられるようにします。

完成図はこちら。リロードしてもテーマは維持されたままになります。

完成図

 

はじめに

テーマをReactのContextで管理し、ユーザが選択することでテーマを切り替えられるようにします。

また、ユーザがどのテーマを選んだかをLocal Storageに保存しておき、リロード時にテーマが保たれるようにします。

ポイント

本記事ではReact Hooksを使っています。

 

準備

以下のGitHubのコードを解説していきます。

 

実装

Material UIテーマの適用

まずはMaterial UIのテーマを適用します。App.jsのreturn文の箇所。

ThemeProviderにtheme={テーマ}でテーマを渡すと全体に適用されます。

return (
  <ThemeProvider theme={ここにテーマ}>
    <CssBaseline />
    <CustomBar />
    <Container maxWidth="sm">
      <DemoContents />
    </Container>
  </ThemeProvider>
);

プロジェクトのthemeフォルダにあるdefaultTheme, darkTheme, navyThemeを渡すとそれぞれテーマを適用することが出来ます。

デモ画面

defaultTheme

 

テーマをContextで管理

次にこのThemeProviderに渡すテーマをReactのContext で管理します。

theme/themeContext.jsは以下のようになります。

import React, { useState } from "react";
import darkTheme from "./darkTheme";
import defaultTheme from "./defaultTheme";
import navyTheme from "./navyTheme";

export const ThemeContext = React.createContext({
  // このように設定するとIDEの補完が機能する
  theme: null,
  handleThemeChange: () => {}
});

const ThemeContextProvider = props => {
  const [theme, setTheme] = useState(defaultTheme);

  const handleThemeChange = themeName => {
    localStorage.setItem("theme", themeName);
    switch (themeName) {
      case "DEFAULT":
        setTheme(defaultTheme);
        break;
      case "DARK":
        setTheme(darkTheme);
        break;
      case "NAVY":
        setTheme(navyTheme);
        break;
      default:
        setTheme(defaultTheme);
        break;
    }
  };

  return (
    <ThemeContext.Provider
      value={{ handleThemeChange: handleThemeChange, theme: theme }}
    >
      {props.children}
    </ThemeContext.Provider>
  );
};

export default ThemeContextProvider;

ThemeContextとThemeContextProviderを作成しています。

ThemeContextは変数の宣言だけ行います。こうすることでIDEの補完が効くようになります。

ThemeContextProviderではuseStateを用いてthemeを管理します。グローバルなuseStateを作成するイメージです。handleThemeChangeは受け取ったテーマ名(DARKなど)に応じてthemeを変更します。

また、themeの変更がされたときにLocal Storageにどのthemeが選択されたか保存しておきます。こうすることでリロードしても、テーマが維持されるようになります。

保存する方法は何でも構いません。CookieでもサーバーのDBに保存してもいいです。

useContextを使ってデフォルトで使用するテーマを設定

次は先程作ったcontextを使っていきます。App.js内に以下のコードを書きます。

const context = useContext(ThemeContext);
useEffect(() => {
  const themeName = localStorage.getItem("theme");
  if (themeName !== null) {
    context.handleThemeChange(themeName);
  }
}, [context]);

React HooksのuseContextを使うことでcontextを簡単に使うことが出来ます。useEffectを使用し、レンダーされたときにLocal Storageに値があればテーマを変更します。

ここまででテーマを「セット」することは出来ました。ここからはテーマの「切り替え」です。

 

テーマを切り替えるためのメニューを設置

AppBarの以下の場所に「テーマを変更」Buttonを配置します。

  <Typography variant="h6" className={classes.title}>
    Demo
  </Typography>
  <Button
    aria-controls="simple-menu"
    aria-haspopup="true"
    color="inherit"
    onClick={handleClick}
  >
    テーマを変更
  </Button>
</Toolbar>

また、Material UIのメニューをCustomBar.jsの中に書きます。

<Menu
        anchorEl={anchorEl}
        keepMounted
        open={Boolean(anchorEl)}
        onClose={handleClose}
      >
        <MenuItem onClick={() => handleMenuClick("DEFAULT")}>
          デフォルト
        </MenuItem>
        <MenuItem onClick={() => handleMenuClick("DARK")}>ダーク</MenuItem>
        <MenuItem onClick={() => handleMenuClick("NAVY")}>ネイビー</MenuItem>
      </Menu>

 

テーマを切り替えられるようにする

以下のようにハンドラを設定します。themeContext.jsで設定したhandleThemeChangeハンドラをメニューがクリックされたときに呼び出しています。

const themeContext = useContext(ThemeContext);

const [anchorEl, setAnchorEl] = React.useState(null);
// テーマを変更ボタンがクリックされたとき
const handleClick = event => {
  setAnchorEl(event.currentTarget);
};
// メニューのクローズ
const handleClose = () => {
  setAnchorEl(null);
};
// メニューがクリックされたら、テーマを設定するハンドラを呼び出す。
// themeNameにはDARKなどの名前が入る。
const handleMenuClick = themeName => {
  themeContext.handleThemeChange(themeName);
  handleClose();
};

以上で完成。

 

完成コード

上にも載せましたが、最終的なコードはこちらから↓

 

まとめ

Material UIでテーマをユーザが切り替えられるようにする方法を紹介しました。

React HooksのuseContextを使用しましたが、Reduxでも同様にstoreで管理すればいいだけです。今回はLocal Storageに保存しましたが、保存する先はCookieでもサーバーのデータベースでもどこでも構いません。

 

これからもReactに関する投稿をしていきますので、よかったらまた来て下さいね。

この記事は参考になりましたか?

下記のボタンよりアンケートにご協力お願いします。

-Webプログラミング
-,

© 2020 エンジニアの本棚