React hooks で複数のcheckboxを実装してみる

## useStateとuseEffectを使って複数のcheckboxを実装してみる

いろいろ手探りで書いてみたのでメモに残しておく。

### 仕様について

- チェックボックスが一つ以上checkedになった場合のみ送信ボタンを表示させたい。
- checkedアイテムのidを、送信ボタンが押されたタイミングで配列で送信したい。

```checkBox.jsx
import React, { useState, useEffect } from "react"

//checkboxのvalueリスト
const checkLists = [
  "パン",
  "おにぎり",
  "焼き肉",
  "ラーメン",
  "たこ焼き",
  "アイスクリーム",
]

//checkboxコンポーネント
const CheckBox = ({id, value, checked, onChange}) => {
  return (
    <input
      id={id}
      type="checkbox"
      name="inputNames"
      checked={checked}
      onChange={onChange}
      value={value}
    />
  )
}

const CheckBoxList = () => {

//checkedItemsは初期値を空のオブジェクトにする
  const [checkedItems, setCheckedItems] = useState({})
//ひとつでもcheckedになっている場合にのみ送信ボタンを表示させたいので、全体のStateを監視する
  const [isBtnHide, setIsBtnHide] = useState(true)

  useEffect(() => {
//checkedItemsが空では無い場合、送信ボタンを表示させる
    Object.keys(checkedItems).length && setIsBtnHide(false)
//すべてのcheckedItemの値がfalseの場合に送信ボタンを表示させる
    setTimeout(() => {
      if (
        Object.values(checkedItems).every(checkedItem => {
          return checkedItem === false
        })
      ) {
        setIsBtnHide(true)
      }
    },100);
  }, [checkedItems])

  const handleChange = e => {
//checkedItemsのstateをセット
    setCheckedItems({
      ...checkedItems,
      [e.target.id]: e.target.checked
    })
    console.log('checkedItems:', checkedItems)
  }

  const dataSendBtn = e => {
//既定のイベントをキャンセルさせる
    e.preventDefault()
//送信ボタンを押したタイミングで、checkedItemsオブジェクトのvalueがtrueのkeyのみを配列にしてconsoleに表示させる
    const dataPushArray = Object.entries(checkedItems).reduce((pre,[key, value])=>{
      value && pre.push(key)
      return pre
    },[])
    console.log("dataPushArray:", dataPushArray)
  }

  return (
    <>
      <h2>好きな食べ物</h2>
      <form>
        {checkLists.map((item, index) => {
          index = index + 1
          return (
            <label htmlFor={`id_${index}`} key={`key_${index}`}>
              <CheckBox
                id={`id_${index}`}
                value={item}
                onChange={handleChange}
                checked={checkedItems[item.id]}
              />
              {item}
            </label>
          )
        })}
{/* checkedがない場合には送信ボタンを表示させない */}
        {!isBtnHide && <button onClick={dataSendBtn}>アンケート送信ボタン</button>}
      </form>
    </>
  )
}

export default CheckBoxList


```

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/218656/c2862e5a-6d63-5f44-bec7-da574b60dc7b.gif" width=800 />


とりあえず意図した通りの動きになった。
でもなんか遠回りしている気がする。。。。

#### new Map()での実装

```jsx
 const [ischecked, toggleChecked] = useState(new Map())
```

Mapはstateを更新するため（再レンダーをトリガーする）に、Mapをnew Mapに置き換える必要がある。

```jsx
const handleChange = e => {
  checkedItems.set(e.target.id, e.target.checked)
  setCheckedItems(new Map(checkedItems) );
  console.log("checkedItems: ", checkedItems);
}
```

#### 20200712追記

`for`を使うのはイケてない。。。と指摘されましたので書き直しました

##### before

```jsx
const dataPushArray = []
for (let [key, value] of Object.entries(checkedItems)) {
  if (value) {
    dataPushArray.push(key)
  }
}
console.log("dataPushArray:", dataPushArray)
```

##### after 
```jsx
const dataPushArray = Object.entries(checkedItems).reduce((pre,[key, value])=>{
  value && pre.push(key)
  return pre
},[])
console.log("dataPushArray:", dataPushArray)
```



もっと良い方法はないか、いろいろ考えてみる、追記していく :relieved: