やりたいこと
ドロップダウン形式のフォームで、ユーザーが選択した項目を利用して、別のフォームの項目を制限する機能を実装したいです。
具体例として、メルカリ等で使われている様なものです。
メンズ | | - バッグ | | | - シューズ - スニーカー | |- 革靴
問題点
onChange
でstateの値が変わった後に、中カテゴリが再レンダー?されない(主な問題点がこれです)setState
が非同期で処理を行っているため、初回のレンダー時にstate
が書き換わっていない
コード
class Add_Give_Item_Form extends Component { constructor(props) { super(props); this.state = { // #インプット情報用 info: { name: '', owner: '', keyword1: '', keyword2: '', keyword3: '', bland: '', state: '未使用、新品', bigCategory:"" , middleCategory:"", smallCategory:"", image: '', detail: '', url: '', }, // Validation用 // urlは必須項目ではないのでValidationには含めない message: { name: '', keyword1: '', keyword2: '', keyword3: '', bland: '', state: '', bigCategory:"" , middleCategory:"", smallCategory:"", image: '', detail: '', url: '', }, allCategory: null, }; this.handleChange = this.handleChange.bind(this); } componentDidMount() { axios .get('http://localhost:8000/api/category') .then((res) => { this.setState( { ...this.state, allCategory: res.data }) }) .catch((err) => { console.log(err); }); axios .get('http://localhost:8000/api/user/' + localStorage.getItem("uid")) .then((res) => { console.log(res) }) .catch((err) => console.log(err)); } handleChange(e) { const name = e.target.name; const value = e.target.value; const { info, message } = this.state; this.setState({ info: { ...info, [name]: value }, }); this.setState({ message: { ...message, [name]: this.validator(name, value) }, }); } // 途中省略 // render() { const { info, message, allCategory } = this.state; // setStateが完了するまではnullにする。 if(this.state.allCategory === null){return null} else { return ( <div> // 途中省略 // <label>カテゴリ</label> <select name="bigCategory" onChange={this.handleChange}> <option value="">大カテゴリ</option> { this.state.allCategory.filter((category) => category.parent === null).map((filteredCategory) => { return ( <option value={filteredCategory}>{filteredCategory.name}</option> ) }) } </select> <select name="middleCategory" onChange={this.handleChange}> <option value="">中カテゴリ</option> { this.state.allCategory.filter((category) => category.parent === this.state.info.bigCategory).map((filteredCategory) => { return (<option value="">{filteredCategory.name}</option>) }) } </select> <label>説明</label> <textarea name="detail" cols="30" rows="10" value={this.state.info.detail} onChange={this.handleChange} ></textarea> </div> ) } } } const mapStateToProps = (state) => { return { uid: state.uid, }; }; export default connect(mapStateToProps)(Add_Give_Item_Form);
詳細
onChange
でstate
の値が変わったタイミング(ユーザーが何らかのカテゴリを選択した時)で、中カテゴリを読み込みたいのですが、再レンダーされません。
流れとしては
1.allCategoryとしてCategory
を全件取得
2.allCategory
をドロップダウンで選ばれた値に応じてfilter
をする
といった形です。
カテゴリーのオブジェクトの中は、以下のようになっています(this.state.allCategoryのvalue
です)。
allCategory
10: {id: 1, parent: null, name: "Men"} 21: {id: 2, parent: null, name: "Women"} 32: {id: 3, parent: "Women", name: "香水"} 43: {id: 4, parent: "Men", name: "ガジェット"} 54: {id: 5, parent: "Men", name: "スポーツ"} 65: {id: 6, parent: "Women", name: "バッグ"} 76: {id: 7, parent: "Women", name: "靴"} 87: {id: 8, parent: "靴", name: "スニーカー"} 98: {id: 9, parent: "スポーツ", name: "ユニフォーム"} 109: {id: 10, parent: "ガジェット", name: "スマートフォン"} 1110: {id: 11, parent: "ガジェット", name: "タブレット"}
大カテゴリの表示については、setState
の非同期処理を制御するために、if(this.state.allCategory === null){return null}
を追加し、なんとか実装ができました。
同じ要領で、bigCategory
がnull
の間は何も返さないように以下のコードを試してみたのですが、中カテゴリは表示されませんでした。
this.state.info.bigCategory == null ? null : this.state.allCategory.filter((category) => category.parent === this.state.info.bigCategory).map((filteredCategory) => { return (<option value="">{filteredCategory.name}</option>) })
また、bigCategory
内に入るオブジェクトではなく、オブジェクトの値(今回はname
)を参照してfilter
をかけても、非同期通信のために、Cannot read property 'name' of null
とエラーが返ってきます。
{ this.state.info.bigCategory === null ? null : this.state.allCategory.filter((category) => category.parent.name === this.state.info.bigCategory.name).map((filteredCategory) => { return (<option value="">{filteredCategory.name}</option>) }) }
どのようにすれば、onChange
でstate
が変わった瞬間に中カテゴリを再レンダーさせることができるのでしょうか?
非同期処理や、setState
の扱い方、レンダーなど知識不足な面が多々あるのは承知ですが、知恵をお貸しいただければ幸いです。
ご回答よろしくお願い申し上げます。
あなたの回答
tips
プレビュー