ツールチップの表示・非表示を切り替えるためにstopPropagationを使った例

## この記事について
ツールチップを表示・非表示にする実装で詰んでたので忘れないようにメモ
文字だけだと分かりづらいので挙動イメージは下の方でcodepenにまとめておいた

## ツールチップ表示・非表示の仕様
1. ツールチップ内の「×」を押すと消える
2. 「？」を押すと表示される
3. ツールチップの外をクリックすると消える
4. ツールチップが開くのは「？」を押したアイテムのみ

## NG挙動
- 連続で「？」をクリックしても反応しない
- ツールチップ内をクリックするとツールチップが消えてしまう

<p class="codepen" data-height="300" data-default-tab="html,result" data-slug-hash="rNGOmrd" data-user="kena-nk" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/kena-nk/pen/rNGOmrd">
  Untitled</a> by kena-nk (<a href="https://codepen.io/kena-nk">@kena-nk</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>

### コードはこんな感じ
```react
function Base() {
  const [question, setQuestion] = React.useState(0);
  const toggleDescription = (e, name) => (question === name ? setQuestion(null) : setQuestion(name));
  const closeDescription = () => {
    if (question !== null) setQuestion(null);
  };

  return (
    <div className="container" onClick={() => closeDescription()}>
      <div className="list">
        <div className="item">
          <div>項目1</div>
          <div className="wrapper">
            <div className="far fa-question-circle" onClick={(e) => toggleDescription(e, 1)} />
            {question === 1 && (
              <div className="description">
                <div>項目1の説明が入るよ</div>
                <div className="far fa-times-circle" onClick={(e) => toggleDescription(e, 1)} />
              </div>
            )}
          </div>
        </div>
~~省略~~
      </div>
    </div>
  );
}
```


## OK挙動
- 連続で「？」をクリックしても反応しない → 解決
- ツールチップ内をクリックするとツールチップが消えてしまう → 解決

<p class="codepen" data-height="300" data-default-tab="html,result" data-slug-hash="VwMvWpv" data-user="kena-nk" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/kena-nk/pen/VwMvWpv">
  ツールチップOK挙動</a> by kena-nk (<a href="https://codepen.io/kena-nk">@kena-nk</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>

### コードはこんな感じ
```react
function Base() {
  const [question, setQuestion] = React.useState(0);
  const toggleDescription = (e, name) => {
    e.stopPropagation();  // e.stopPropagation();追加したよ

    return question === name
      ? setQuestion(null)
      : setQuestion(name);
  };
  const closeDescription = () => {
    if (question !== null) setQuestion(null);
  };

  return (
    <div className="container" onClick={() => closeDescription()}>
      <div className="list">
          <div className="item">
            <div>項目1</div>
            <div className="wrapper">
              <div className="far fa-question-circle" onClick={(e) => toggleDescription(e, 1)} />
              {question === 1 && (
              // onClick={(e) => e.stopPropagation()}追加したよ
              <div className="description" onClick={(e) => e.stopPropagation()}>
                <div>項目1の説明が入るよ</div>
                <div className="far fa-times-circle" onClick={(e) => toggleDescription(e, 1)} />
              </div>
              )}
            </div>
          </div>
~~省略~~
        </div>
      </div>
  );
}
```
## 今回の原因
一番外側のDOMに仕掛けてる`closeDescription()`がクリックのたびに実行されていることが問題点だった。
本当は内側のDOMごとに仕掛けている`toggleDescription()`が発火されてほしいが、親のイベント実行が邪魔してうまくいってないという感じだった。

流れに起こすとこんな感じ↓

```
1. 項目1の「？」クリック

2. toggleDescription() が発火して question に 1 がセットされる

3. closeDescription() が発火するが question は null ではないので何も起こらない

4. 項目2の「？」クリック

5. toggleDescription() が発火して question に 2 がセットされる

6. closeDescription() が発火して question は値がセットされている為 question に null セットされる
```

なので今回は親の実行を阻止する[stopPropagation()](https://developer.mozilla.org/ja/docs/Web/API/Event/stopPropagation)を仕掛けることで回避した。

## まとめ
イベント発火阻止系は[preventDefault()](https://developer.mozilla.org/ja/docs/Web/API/Event/preventDefault)しか使ったことがなく色々疎かったので使う機会が来てよかった。めちゃめちゃ有難い関数・・！
これ以外にも親のイベントを阻止する方法などあればどなたか教えてください🙏