投稿目的
- よりよい方法をご教授いただきたい
- 「ふわっとした理解」を「ぷにっとした理解」くらいに固める
本題
きっかけ
個人開発内(TypeScript×React)で下記のような実装をしたいと思いました。
・チェックボックスが
checkedの間、<input type='text' />を表示する
・チェックボックスがcheckedではない間、<input type='text' />を表示しない
何もせず「&&」でコンポーネントを非表示にすると...
&&演算子は、「左辺がtruthyなら右辺を返却する」「左辺がfalsyなら左辺を返却する」演算子です。
・truthy
→ falsy以外の値
・falsy
-string型の「""(empty-string)」
-number型の「0」
-boolean型の「false」
-null
-undefined
-NaN(Not a Number)
(参考)
これを用いると、下記のような実装で表示制御が可能となります。(styled-componentsによるスタイル定義については省略)
Stack Blitz
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>
</>
);
};
isVisibleがtrueの間は<input type="text" />が表示されていることが分かります。
しかし、何かを入力(①)してからチェックを外し、再度チェックをつけると、①の入力内容が消えていることが分かります。
これは、isVisibleがfalseになっている間、<input type="text" />がunmountされている (HTMLから消えている) ことが原因です。
・isVisibleがtrueの間のHTML
<div>
<div>
<input type="checkbox" checked />
<label>表示を制御する</label>
</div>
<!-- 表示されている -->
<input type="text" />
</div>
・isVisibleがfalseの間の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プロパティに値を設定することができます。
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>
</>
);
};
チェックを一度外した後にもう一度つけてみると、入力内容が保持されていることが分かります。
【解決策】邪道(?) CSSでどうにかする
CSSでdisplay: none;やvisibitity: hidden; height: 0;などを表示制御したい範囲に設定すると、当該範囲が非表示になってくれます。
しかも、unmountされたわけではないため、HTML上からは消えず、stateでvalueプロパティを保持しなくてもvalueプロパティの値が消えません。
(参考)
私の場合、<HiddenArea />というcomponentを作成し、その子要素(children)に表示制御したい要素を設定しました。
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>
);
};
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に変化し、表示制御が行われる
という実装になっていました。
雑談って大事なんだな~と思いました、雑談を大事にします。(小並感)

