Material UIで複数のテーマをユーザが切り替えられるようにします。
完成図はこちら。リロードしてもテーマは維持されたままになります。
Contents
はじめに
テーマを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を渡すとそれぞれテーマを適用することが出来ます。
テーマを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に関する投稿をしていきますので、よかったらまた来て下さいね。