TS×React componentをunmountせずに、非表示にしつつも入力内容を保持したい男

# 投稿目的
<ul>
<li>よりよい方法をご教授いただきたい</li>
<li>「ふわっとした理解」を「ぷにっとした理解」くらいに固める
</ul>

# 本題
### きっかけ
個人開発内(TypeScript×React)で下記のような実装をしたいと思いました。
>・チェックボックスが`checked`の間、`<input type='text' />`を表示する
>・チェックボックスが`checked`ではない間、`<input type='text' />`を表示しない

### 何もせず「&&」でコンポーネントを非表示にすると...
`&&`演算子は、「左辺がtruthyなら右辺を返却する」「左辺がfalsyなら左辺を返却する」演算子です。
>・truthy
&nbsp;&nbsp;&nbsp;&nbsp;→ falsy以外の値
>・falsy
>&nbsp;&nbsp;&nbsp;&nbsp;- `string`型の「`""`(empty-string)」
>&nbsp;&nbsp;&nbsp;&nbsp;- `number`型の「`0`」
>&nbsp;&nbsp;&nbsp;&nbsp;- `boolean`型の「`false`」
>&nbsp;&nbsp;&nbsp;&nbsp;- `null`
>&nbsp;&nbsp;&nbsp;&nbsp;- `undefined`
>&nbsp;&nbsp;&nbsp;&nbsp;- `NaN`(Not a Number)
><a href="https://www.freecodecamp.org/news/what-are-falsey-values-in-javascript/">(参考)</a>

これを用いると、下記のような実装で表示制御が可能となります。(`styled-components`によるスタイル定義については省略)
<a href="https://stackblitz.com/edit/vitejs-vite-qxoxxx?file=src%2Fpages%2FaboutUnmount%2Fcomponents%2FInitialPage.tsx">Stack Blitz</a>

``` TypeScript
import { FC, useState } from 'react';
import styled from 'styled-components';


export const DefaultPage: FC = () => {
  const [isVisible, setIsVisible] = useState<boolean>(false);

  return (
    <>
      <SContainer>
        <SCheckboxArea>
          <input
            type="checkbox"
            id="mr-checkbox"
            onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
              setIsVisible(e.currentTarget.checked);
            }}
          />
          <label htmlFor="mr-checkbox">表示を制御する</label>
        </SCheckboxArea>
        {/** 「&&」で表示制御 */}
        {isVisible && <input type="text" />}
      </SContainer>
    </>
  );
};
```
![Videotogif(1).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3519885/7846f49e-c1e3-a6b1-8cff-62f1a36c6113.gif)

`isVisible`が`true`の間は`<input type="text" />`が表示されていることが分かります。
しかし、何かを入力(①)してからチェックを外し、再度チェックをつけると、①の入力内容が消えていることが分かります。

これは、`isVisible`が`false`になっている間、`<input type="text" />`がunmountされている (HTMLから消えている) ことが原因です。

・`isVisible`が`true`の間のHTML
``` HTML
  <div>
    <div>
      <input type="checkbox" checked />
      <label>表示を制御する</label>
    </div>
    <!-- 表示されている -->
    <input type="text" />
  </div>
```

・`isVisible`が`false`の間のHTML
``` HTML
  <div>
    <div>
      <input type="checkbox" />
      <label>表示を制御する</label>
    </div>
    <!-- <input type='text' />が消えている -->
  </div>
```

`isVisible`が`false`から`true`に切り替わるたびに、新たな`<input type='text' />`が生成・描画されているイメージです。

### 【解決策】王道 `state`を使う
`<input type='text' />`に`onChange`ついてねーじゃん と思われた方も多いと思います。
unmount後も入力した値が`state`に保持されるため、再度`<input type='text' />`がmountされた (HTML上に描画された) 際に`value`プロパティに値を設定することができます。

<a href="https://stackblitz.com/edit/vitejs-vite-qxoxxx?file=src%2Fpages%2FaboutUnmount%2Fcomponents%2FStatePage.tsx">Stack Blitz</a>

``` TypeScript
import { FC, useState } from 'react';
import styled from 'styled-components';


export const StatePage: FC = () => {
  const [isVisible, setIsVisible] = useState<boolean>(false);

  // 入力値をstateで保持
  const [inputValue, setInputValue] = useState<string>("");

  return (
    <>
      <SContainer>
        <SCheckboxArea>
          <input
            type="checkbox"
            id="mr-checkbox"
            onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
              setIsVisible(e.currentTarget.checked);
            }}
          />
          <label htmlFor="mr-checkbox">表示を制御する</label>
        </SCheckboxArea>
        {/** 「&&」で表示制御 */}
        {isVisible && (
          <input
            type="text"
            value={inputValue}
            onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
              setInputValue(e.currentTarget.value);
            }}
          />)
        }
      </SContainer>
    </>
  );
};
```

![Videotogif(2).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3519885/d82c1b23-dfff-25c6-b739-5040da10f970.gif)

チェックを一度外した後にもう一度つけてみると、入力内容が保持されていることが分かります。


### 【解決策】邪道(?) CSSでどうにかする
CSSで`display: none;`や`visibitity: hidden; height: 0;`などを表示制御したい範囲に設定すると、当該範囲が非表示になってくれます。
しかも、unmountされたわけではないため、HTML上からは消えず、`state`で`value`プロパティを保持しなくても`value`プロパティの値が消えません。
<a href="https://developer.mozilla.org/en-US/docs/Web/CSS/visibility">(参考)</a>

私の場合、`<HiddenArea />`というcomponentを作成し、その子要素(`children`)に表示制御したい要素を設定しました。

<a href="https://stackblitz.com/edit/vitejs-vite-qxoxxx?file=src%2Fpages%2FaboutUnmount%2Fcomponents%2FCssPage.tsx">Stack Blitz</a>

```TypeScript
import { FC, ReactNode, memo } from "react";
import styled from "styled-components";

type HiddenAreaProps = {
	/**
	 * 子要素
	 * @type {ReactNode}
	 */
	children: ReactNode;

	/**
	 * 見えているか
	 * @type {boolean}
	 */
	isVisible: boolean;
};

export const HiddenArea: FC<HiddenAreaProps> = (props: HiddenAreaProps) => {
  const { children, isVisible } = props;

  return (
    {/** isVisibleがfalseの場合、display:none; で非表示 */}
	<SContainer style={isVisible ? {} : { display: "none" }}>
	  {children}
	</SContainer>
  );
};
```

``` TypeScript
import { FC, useState } from 'react';
import styled from 'styled-components';
import { HiddenArea } from '../../aboutUnmount/HiddenArea';

export const CssPage: FC = () => {
  const [isVisible, setIsVisible] = useState<boolean>(false);

  return (
    <>
      <SContainer>
        <SCheckboxArea>
          <input
            type="checkbox"
            id="mr-checkbox"
            onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
              setIsVisible(e.currentTarget.checked);
            }}
          />
          <label htmlFor="mr-checkbox">表示を制御する</label>
        </SCheckboxArea>
        {/** CSSで表示制御 */}
        <HiddenArea children={<input type="text" />} isVisible={isVisible} />
      </SContainer>
    </>
  );
};
```
こちらも、`state`を用いた場合と同じ結果になります。

# まとめ
`<input />`の`value`操作×表示制御 には`state`を用いるのが一般的ですが、それ以外にも方法はあるんだよ～というご紹介でした。

`state`を用いると入力値をリアルタイムで把握できるため、「入力値が5桁未満の場合はボタンを非活性にする」「全角スペースが入力された場合、半角スペースに変換して入力値に反映する」といったことが可能になります。
ただ、`state`として管理したい変数が多くなってくるとコードが煩雑になったり、システムが重くなってしまうことがあります...

今回紹介した方法は、入力値のリアルタイムな把握はできませんが、「ボタンがクリックされた時の入力値を取得する」などといった実装は可能です。

>【イメージ】
>①ボタンをクリックし、入力値を取得
>②入力値のバリデーションや整形を実行
>③APIのパラメータとしてセット

# 余談
この方法を思い出したのは、現場での経験によるものです。
ある日先輩に「ハンバーガメニューの表示制御ってどうやってたんだっけ? `state`によって出し分けてたっけ?」と質問をいただき確認すると、
>①アイコンをクリックすると、`isOpen` (`state`で管理されている変数) が`true`となる
>②`isOpen`によって、ハンバーガメニューに適用される`className`が変化する
>③`isOpen`が`true`の時は、`visibility: visible;`が、`false`の時は`visibility: collapse;`が適用される`className` に変化し、表示制御が行われる

という実装になっていました。
雑談って大事なんだな~と思いました、雑談を大事にします。(小並感)