6
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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

Last updated at Posted at 2024-06-08

投稿目的

  • よりよい方法をご教授いただきたい
  • 「ふわっとした理解」を「ぷにっとした理解」くらいに固める

本題

きっかけ

個人開発内(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>
    </>
  );
};

Videotogif(1).gif

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

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

isVisibletrueの間のHTML

  <div>
    <div>
      <input type="checkbox" checked />
      <label>表示を制御する</label>
    </div>
    <!-- 表示されている -->
    <input type="text" />
  </div>

isVisiblefalseの間のHTML

  <div>
    <div>
      <input type="checkbox" />
      <label>表示を制御する</label>
    </div>
    <!-- <input type='text' />が消えている -->
  </div>

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

【解決策】王道 stateを使う

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

Stack Blitz

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

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

【解決策】邪道(?) CSSでどうにかする

CSSでdisplay: none;visibitity: hidden; height: 0;などを表示制御したい範囲に設定すると、当該範囲が非表示になってくれます。
しかも、unmountされたわけではないため、HTML上からは消えず、statevalueプロパティを保持しなくてもvalueプロパティの値が消えません。
(参考)

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

Stack Blitz

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が変化する
isOpentrueの時は、visibility: visible;が、falseの時はvisibility: collapse;が適用されるclassName に変化し、表示制御が行われる

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

6
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?