React: React Bootstrapでタブのカラーを変える ー useState()とuseEffect()を使って

[React Bootstrap](https://react-bootstrap.github.io/)は、Bootstrapのスタイルが適用されたコンポーネントをReactアプリケーションに組み込めるライブラリです。この3月24日に正規バージョン[v1.0.0がリリース](https://github.com/react-bootstrap/react-bootstrap/releases/tag/v1.0.0)されました。Bootstrapのみでは`<div>`などにやたらとクラスを詰め込んだDOM要素があふれます。それに対して、Reactのコンポーネントですっきりと組み立てられるのが利点です。

#### Bootstrap「[Jumbotron](https://getbootstrap.com/docs/4.4/examples/jumbotron/)」のコード例
```html
<div class="collapse navbar-collapse" id="navbarsExampleDefault">
	<ul class="navbar-nav mr-auto">
		<li class="nav-item active">
			<a class="nav-link" href="#">Home <span class="sr-only">(current)</span></a>
		</li>
		<li class="nav-item">
			<a class="nav-link" href="#">Link</a>
		</li>
		<li class="nav-item">
			<a class="nav-link disabled" href="#" tabindex="-1" aria-disabled="true">Disabled</a>
		</li>
		<li class="nav-item dropdown">
			<a class="nav-link dropdown-toggle" href="#" id="dropdown01" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">Dropdown</a>
			<div class="dropdown-menu" aria-labelledby="dropdown01">
				<a class="dropdown-item" href="#">Action</a>
				<a class="dropdown-item" href="#">Another action</a>
				<a class="dropdown-item" href="#">Something else here</a>
			</div>
		</li>
	</ul>
```

#### React Bootstrapで[再現したコード例](https://codesandbox.io/s/react-bootstrap-jumbotron-example-vs9xh?file=/src/App.js:316-1347)
```react
<Navbar.Collapse id="basic-navbar-nav">
    <Nav className="mr-auto">
        <Nav.Link href="#home" active>
            Home
        </Nav.Link>
        <Nav.Link href="#features">Link</Nav.Link>
        <Nav.Link href="#pricing" disabled>
            Disabled
        </Nav.Link>
        <NavDropdown title="Dropdown" id="basic-nav-dropdown">
            <NavDropdown.Item href="#action">Action</NavDropdown.Item>
            <NavDropdown.Item href="#another">
                Another action
            </NavDropdown.Item>
            <NavDropdown.Item href="#something">Something</NavDropdown.Item>
            <NavDropdown.Divider />
            <NavDropdown.Item href="#separated">
                Separated link
            </NavDropdown.Item>
        </NavDropdown>
    </Nav>
```

とはいえ、コンポーネントの中身がブラックボックスになってしまって、手をつけにくいことも少なくありません。タブ(`Tabs`や`Tab`)はその典型です。今回はこの[作例](https://codesandbox.io/s/react-bootstrap-changing-tab-colors-legxo?file=/src/App.js)のように、タブのカラーを動的に変えてみようとした試みのご紹介です([set]と[reset]ボタンでタブのカラーが設定・解除できます)。

# CSSクラスでスタイルを定める
Reactアプリケーションは、`create-react-app`でひな形をつくることにします。ひな形Reactアプリケーションのつくり方は「Create React App 入門 01: 3×3のマス目をつくる」01「[Reactアプリケーションのひな形をつくる](http://www.fumiononaka.com/Business/html5/FN1912002.html#sec01)」をお読みください。

さらに、React BootstrapとBootstrapをインストールします(「[Introduction](https://react-bootstrap.github.io/getting-started/introduction)」参照)。

```terminal
npm install react-bootstrap bootstrap
```

React Bootstrapサイトの[Tabbed components](https://react-bootstrap.github.io/components/tabs/)を参考に、モジュール`src/App.js`はつぎのように書き替えます。これで、まずはタブが表示されるはずです(図001)。BootstrapのCSSライブラリの読み込みが必要なことにもご注意ください。

```react:src/App.js
import React from 'react';
import {
	Tabs,
	Tab,
	ButtonGroup,
	ToggleButton,
	Container,
} from 'react-bootstrap';
import 'bootstrap/dist/css/bootstrap.min.css';
import './App.css';

function App() {
	const tabsId = 'uncontrolled-tab-example';
	return (
		<Tabs defaultActiveKey="home" id={tabsId}>
			<Tab eventKey="home" title="Home">
				Home
			</Tab>
			<Tab eventKey="profile" title="Profile">
				Profile
			</Tab>
			<Tab eventKey="contact" title="Contact" disabled>
				Contact
			</Tab>
		</Tabs>
	);
}

export default App;
```

### 図001■タブが表示された
![2004001_04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128949/ef8d9017-11f4-31a0-946a-03008d1ae9a7.png)

ブラックボックスでまず悩むのは、コンポーネント`Tab`にクラスやスタイルを定めても、何の反応もないことです([`Tab`](https://github.com/react-bootstrap/react-bootstrap/blob/master/src/Tab.js)は抽象コンポーネントだからです)。ただ、ブラウザのデベロッパーツールで確かめると、タブの要素にはクラス`nav-item`と`nav-link`が与えられていることはわかります。では、モジュール`src/App.css`にスタイルを定めて確かめましょう。

```css:src/App.css
.nav-item.nav-link {
	background-color: aqua;
}
```

タブのカラーは変わるものの、`active`と`disabled`についてはスタイルが上書きされてしまうようです(図002)。

#### 図002■タブのカラーがCSSで変わった
![2004001_01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128949/2478f003-bb87-888d-720b-7600be3a0e01.png)


# 動的にタブのスタイルを変える
今回は、タブのスタイル(カラー)を動的に変えたいと思います。ですから、`src/App.css`の設定は一旦消してください。そして、モジュール`src/App.js`にボタングループ(`ButtonGroup`)を以下のように加えます。ブラウザのデベロッパーツールでもうひとつわかることは、タブに「`Tabsのid + '-tab-' + TabのeventKey`」という`id`が定められていることです。

そこで、タブごとのカラー(`tabColor`)は配列(`tabs`)で定めて、`ButtonGroup`の`onClick`イベントでタブのDOM要素にBootstrapのクラスを定めてみましょう(`eventKey`はプロパティ`title`に与えました)。

```react:src/App.js
function App() {
	const tabs = [
		{title: 'home', tabColor: ['text-white', 'bg-primary']},
		{title: 'profile', tabColor: ['text-white', 'bg-success']},
		{title: 'contact', tabColor: ['bg-warning']},
	];
	const tabsId = 'uncontrolled-tab-example';
	const setColor = (event) => {
		const value = event.target.value;
		if (value) {
			console.log('value:', value);
			tabs.forEach((tab) => {
				const tabElement = document.getElementById(tabsId + '-tab-' + tab.title);
				switch (value) {
					case '2':
						tabElement.classList.add(...tab.tabColor);
						break;
					case '1':
						tabElement.classList.remove(...tab.tabColor);
						break;
					default:
						break;
				}
			});
		}
	};
	return (
		<Tabs defaultActiveKey="home" id={tabsId}>
			<Tab eventKey="home" title="Home">
				<Container className="pt-2">
					<ButtonGroup toggle onClick={setColor}>
						<ToggleButton type="radio" name="radio" defaultChecked value="1">
							reset
						</ToggleButton>
						<ToggleButton type="radio" name="radio" value="2">
							set
						</ToggleButton>
					</ButtonGroup>
				</Container>
			</Tab>

		</Tabs>
	);
}
```

これで、設定ボタン[set]をクリックすると、タブごとにそれぞれのカラーが加えられ、解除の[reset]ボタンでもとに戻ります(図003)。

#### 図003■タブごとにカラーが設定される
![2004001_02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128949/3d155c35-c6c7-6c98-7fa6-d7c560ed83e3.png)

ただし、カラーを与えたあとタブを切り替えると、それらのタブの色が戻ってしまいます(図004)。

### 図004■タブを切り替えると設定したカラーがもとに戻る
![2004001_03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128949/236cd77d-8916-59a4-a02e-ffb6c6b499f1.png)

では、つぎのようにタブ(`Tabs`)をクリックしたとき、改めてカラーを設定し直せばよいのではないでしょうか。残念ながら、これは失敗します。カラーを再設定したあとに、タブ切り替えのスタイルで上書きされるようです。なお、カラーが設定されているか、リセットされたかを[ステートフック](https://ja.reactjs.org/docs/hooks-state.html)と呼ばれる[`useState()`](https://ja.reactjs.org/docs/hooks-reference.html#usestate)関数で`state`変数(`mode`)に定めました(「React Hooks: クラスのコンポーネントをuseState()で関数に書き替える」02「[ステートフックuseState()を使う](http://www.fumiononaka.com/Business/html5/FN2004003.html#sec02)」参照)。

```react:src/App.css
import React, { useState } from 'react';

function App() {
	const [mode, setMode] = useState('1');

	const setColor = (event) => {

		if (value) {
			setMode(value);
			setTabsColor(value);
		}
	};
	const setTabsColor = (value) => {
		tabs.forEach((tab) => {
			const tabElement = document.getElementById(tabsId + '-tab-' + tab.title);
			switch (value) {
				case '2':
					tabElement.classList.add(...tab.tabColor);
					break;
				case '1':
					tabElement.classList.remove(...tab.tabColor);
					break;
				default:
					break;
			}
		});
	};
	return (
		<Tabs defaultActiveKey="home" id={tabsId} onClick={() => setTabsColor(mode)}>

		</Tabs>
	);
}
```

Reactコンポーネントツリーがレンダーされたあとに、カラーは再設定しなければなりません。そのために使えるのが、[副作用フック](https://ja.reactjs.org/docs/hooks-effect.html)の[`useEffect()`](https://ja.reactjs.org/docs/hooks-reference.html#useeffect)関数です。

> `useEffect`に渡された関数はレンダーの結果が画面に反映された後に動作します。
> (「[useEffect](https://ja.reactjs.org/docs/hooks-reference.html#useeffect)」より)

`useEffect()`の第1引数は、行うべき処理をコールバックとして定めた副作用関数です。そして、第2引数は依存配列と呼ばれ、その変化により副作用関数を発動する変数(`changed`)が収められます。

```react:src/App.css
import React, { useState, useEffect } from 'react';

function App() {

	const [changed, setChanged] = useState(false);
	useEffect(() => {
		setTabsColor(mode);
		setChanged(false);
	}, [changed]);

	return (
		<Tabs defaultActiveKey="home" id={tabsId} onClick={() => setChanged(true)}>

		</Tabs>
	);
}
```

これでカラーを加えたあとタブの切り替えによりスタイルが変わっても、副作用関数がレンダー後に設定し直します。動きは、冒頭にもご紹介した[CodeSandboxの作例](https://codesandbox.io/s/react-bootstrap-changing-tab-colors-legxo?file=/src/App.js)でお確かめださい。