以前Reactの研修を受けて頂いた企業向けに、現在Reactプロダクトの開発支援を行っています。 そこで、出た問題が実はReactの深い問題に触れていたので、解説記事を書きます。
DALL·Eで生成したuncontrolled to controlledの画像
現象
問題は、以下のようなMUI(Material UI)のAutocompleteという文字入力要素(TextField, input)にサジェスチョン付きの選択肢を追加できるコンポーネントを使ったコードで起きます。起動1秒後に選択肢が設定されますが、その際に初期値2が表示されそうなのですが、表示されません。
- ① 選択肢のStateです。型は文字列の配列で初期値は空配列です
- ② アプリ起動1秒後に選択肢が設定されます(本物のコードではバックエンドと通信して選択肢を取得しています)
- ③ Autocompleteに選択肢を設定
- ④ Autocompleteの初期値は、選択が2に等しいものを設定(本物のコードでは前回設定した選択肢を設定しています)
import { useEffect, useState } from 'react';
import { Autocomplete, TextField } from '@mui/material';
const App = () => {
const [list, setList] = useState<string[]>([]); // ← ①
useEffect(() => {
setTimeout(() => setList(["1", "2", "3", "4"]), 1000); // ← ②
}, []);
return (
<Autocomplete
options={list} // ← ③
value = {list.find(e => e === "2")} // ← ④
renderInput={(params) => <TextField {...params} />}
/>
)
}
export default App
そして、このアプリを起動1秒後にブラウザーのコンソールには以下のようなエラーが表示されます。
MUI: A component is changing the uncontrolled value state of Autocomplete to be controlled.
Elements should not switch from uncontrolled to controlled (or vice versa).
Decide between using a controlled or uncontrolled Autocomplete element for the lifetime of the component.
The nature of the state is determined during the first render. It’s considered controlled if the value is notundefined
.
最初の行をGoogle翻訳すると以下のようになります。何を言いたいのかわかりますか? 謎のメッセージですよね。😅
MUI: コンポーネントは、オートコンプリートの制御されていない値の状態を制御されるように変更しています。
controlled component vs uncontrolled component
上のメッセージを見て、controlled/uncontrolledは一般的な言葉ではなく、Reactのcontrolled component(制御されたコンポーネント) / uncontrolled component(非制御コンポーネン)の事だと気が付いた人は、ちゃんとReactを勉強した人ですね❗
改めて解説すると、controlled componentは下のコードのようにinputタグで入力された文字列を明示的にState管理する、Reactらしいコードです。
import { useState } from 'react'
const App = () => {
const [text, SetText] = useState("");
return (
<form onSubmit={e => {
console.log(`input = ${text}`); e.preventDefault();}}>
<input type="text" value={text} onChange={e => SetText(e.target.value)} />
<input type="submit" />
</form>
)
}
export default App
一方、uncontrolled componentは入力処理はブラウザーのinputタグに任せ、useRefを使いブラウザーのDOMにアクセスし入力値を取得しています。
import { useRef } from 'react'
const App = () => {
const inputRef = useRef<HTMLInputElement>(null);
return (
<form onSubmit={e => {
console.log(`input = ${inputRef.current?.value}`); e.preventDefault();}}>
<input type="text" ref={inputRef} />
<input type="submit" />
</form>
)
}
export default App
問題の解説
さて今回の問題ですが、React/JavaScriptの特性・仕様が組み合わさることで発生しています。
・ Autocompleteの特性
コンソールに表示されるエラーメッセージの1行目は、
uncontrolled componentであるAutocompleteコンポーネントのvalue属性を設定することで、controlled componentに変更しようとしています。
のような意味でしょうか、2行目は
uncontrolled componentからcontrolled componentに切り替えるべきではありません(その逆も)。
さらに、3,4行目には
Autocompleteコンポーネントの実行時にはcontrolledかuncontrolledを決めておかないといけません。 初期レンダリングの時にvalue属性が
undefined
で無ければcontrolled componentになります
のように説明してくれています。😁
・ find()メソッドの仕様
配列のfind()メソッドは、条件に合う項目が無い場合はundefined
が戻ります!
今回の問題コードでは、初期描画時にはlistは空なので、find()メソッドは確実にundefined
が戻します。
$ node
> ["1", "2", "3", "4"].find(e => e === "2")
'2'
> [].find(e => e === "2")
undefined
>
ということで、今回の問題は以下の2つが組み合わさって起きたのです。
- Autocompleteのvalue=属性の初期値が
undefined
の場合、uncontrolled componentになり、以後value=属性が設定されても値は設定されない list.find(e => e === "2")
条件に合う項目が無い場合はundefined
が戻る
解決案
解決案はいくつかあると思いますが、以下のコードでは
④のところで、find()メソッドがundefined
を戻した場合はnull
をvalue属性に設定し、Autocompleteをcontrolled componentに設定しています。これでvalueの設定が有効になります。
import { useEffect, useState } from 'react';
import { Autocomplete, TextField } from '@mui/material';
const App = () => {
const [list, setList] = useState<string[]>([]);
useEffect(() => {
setTimeout(() => setList(["1", "2", "3", "4"]), 1000);
}, []);
return (
<Autocomplete
options={list}
value = {list.find(e => e === "2") ?? null} // ← ④
renderInput={(params) => <TextField {...params} />}
/>
)
}
export default App
まとめ
今回のバグは、なかなか手強いバグでした。しかしコンソールには問題点や解決方法が表示されていました。やはりエラーメッセージはちゃんと読まないといけませんね。英語ではテクニカルタームに一般的な単語を使う事が多いので、今回のようにGoogle翻訳やDeeplでは上手く翻訳できない事もあります。
また、エラーメッセージでGoogle検索してきた日本語の情報も説明不足のものが多く、やはりStack Overflowや英語のブログ記事には良い記事もありました(でも読むのが面倒ですね😅)。
EY-OfficeではReactの入門教育だけではなく、開発支援も行っています。はじめてReactを実プロジェクトに導入するさいには色々な問題が発生します、多くは初心者には解決に時間がかかります。そのような際にはEY-Officeのお問い合わせください。