TextControlに日本語入力ができなくなった！？

## どんな状態になったか
いつものようにブロック開発をしていました。何気なく以前作ったブロックに文字入れをしようと思ってTextControlに日本語を入れようと思うと次の画像のような現象が起きました。


![固定ページを編集 “コンタクトフォームブロック用の固定ページ” ‹ ブロックテストのテーマ — Wo.png](https://itmaroon.net/home/wp-content/uploads/2023/08/b21c841e2c614a83834912fafcd11e19.png)

この画像でおわかりでしょうか？
この画像はブロックエディタのサイドバーです。
母音はローマ字変換されているけど子音は母音の入力を待たず入力が確定しているという状態です。これまで見たことがない怪現象です。
IMEの設定がおかしくなったかと思い確認しましたが特に問題はありません。そもそも他のTextControlではこんな現象は起きていません。
よくみると漢字変換もできない状態です。つまり、一文字入力するごとにEnterキーを押したような状態です。
TextControlに何か変なPropsでも渡したかなと思って確認しても特に何もありません。
こんな時はChatGPTかと思って現象を説明しましたしたがReactの非同期処理の問題というような回答でイマイチ的を得ていませんでした。
## TextControlのレンダリングコード
TextControlをレンダリングしているコードは次のようになっています。
```
<TextControl
	label="コピーテキスト"
	labelPosition="top"
	value={optionStyle.copy_content}
	onChange={(newValue) => {
		setLocalOptionStyle(prev => ({ ...prev, copy_content: newValue }));
	}}
/>
```
上記のコードの`onChange={(newValue) => {setLocalOptionStyle(prev => ({ ...prev, copy_content: newValue }));`でこのブロックコンポーネントが持つoptionStyleオブジェクト内のcopy_content オブジェクトの値を書き換えています。
そして、
```
const [localOptionStyle, setLocalOptionStyle] = useState(optionStyle);

// localOptionStyle の変更があるたびに setAttributes を呼び出す
useEffect(() => {
	setAttributes({ optionStyle: localOptionStyle });
}, [localOptionStyle]);

```
としてあって、setLocalOptionStyleでlocalOptionStyleに変更があれば、optionStyleというブロックの属性が書き換わるという仕組みです。
特別特殊なことは何もしてないし、何か問題が起こるなどとは全く思っていませんでした。
ところが、このコードには重大な欠陥があるのです。おわかりでしょうか？

## TextControlのonChangeは一文字入力ごとに発火する
ヒントはこの表題の中にあります。
答えに至るポイントは
```
value={optionStyle.copy_content}
```
にありました。
どうしても
```
onChange={(newValue) => {
	setLocalOptionStyle(prev => ({ ...prev, copy_content: newValue }));
}}
```
の方に目が行きがちでChatGPTも`onChange`でなく`onBlur`で処理すれば、入力の確定を遅らせることができるなんていう説明をしていました。
少し説明を加えると`onChange`というイベントは文字を入力する度に起きるのですが、`onBlur`はインプットボックスからフォーカスが外れないと発生しません。つまり、文字の入力の度にオブジェクトの値を更新してるのが悪いというわけです。
しかし、それはどんな`TextControl`でも同じでしょう。なぜ特定の`TextControl`でだけこの現象が起きるのでしょうか。

そこの問題ではないのです。`onChange`を`onBlur`に変えても結果は変わりません。

 そもそも、TextControlに表示されている文字列は`value`に渡された値です。
 次のようなコードがより一般的だと思います。
```
<TextControl
	value={copyInputValue}
	onChange={(newValue) => {
		setCopyInputValue(newValue);
	}}
/>
```
このコードと問題のコードの違いがおわかりでしょうか。
そう、一般的なコードは`value`に`newValue`が何も加工されずに渡っています。ところが、問題のコードは`value`に`{ optionStyle.copy_content }`という`newValue`を加工したものを表示させています。
そうすると**TextControlは未変換の状態を維持することができず、確定した文字列を表示してしまう**のです。
「TextControlのonChangeは一文字入力ごとに発火する」ということを意識していれば、そのたびに`setLocalOptionStyle(prev => ({ ...prev, copy_content: newValue }));`が働いて一文字一文字確定させていくんだというイメージが湧いたのではないでしょうか。
たとえば次のようなコードならわかりやすいでしょう。
```
<TextControl
	value={copyInputValue}
	onChange={(newValue) => {
		setCopyInputValue(`${newValue}.`);
	}}
/>
```
こんなコードを書くことはないでしょうが、テンプレートリテラルで`newValue`に’.'をつけるような加工をしています。これがTextControl内にレンダリングされたら、その後は日本語変換はできなくなるだろうなということは想像がつくと思います。

## それでどうやって問題を解消するか
この問題を解消するコードを示します。
```
//TextControlの表示用変数
const [copyInputValue, setCopyInputValue] = useState(optionStyle.copy_content);

<TextControl
	label="コピーテキスト"
	labelPosition="top"
	value={copyInputValue}
	onChange={(newValue) => {
		setCopyInputValue(newValue);
		setLocalOptionStyle(prev => ({ ...prev, copy_content: newValue }));
	}}
/>
```
このコードではTextControlの表示用として状態変数を一つ用意して、それを`value`に渡しています。そうすることでTextControlの表示として加工していない`newValue`を渡すことができます。setLocalOptionStyleはこれまでどおり行えばよいのです。ブロックにレンダリングされるのはそれによって書き換えられた`optionStyle.copy_content`です。それ自身は正解で、何も`onBlur`のイベント発生を待つ必要はありません。それを待っているとブロックへのレンダリングが遅れ、TextControlの入力内容がリアルタイムにブロックの表示に反映されなくなります。
## まとめ
TextControlは非常に一般的で基本的なUIだと思っていましたが、他の入力コントロールと違い入力途中の状態を表示するという特殊性があります。そのため入力コントロールの表示とブロックのレンダリングが必ずしも一致しないということを意識しないといけないのかということに気付きました。もちろん、ほとんど場合は一致するので気が付きにくいです。日本語入力でなく、英数字を入力していればこんな現象は起こらず欠陥には気づきません。
そういう意味でなかなか、良い勉強になったと思いました。
参考にしていただけると光栄です。