質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
React.js

Reactは、アプリケーションのインターフェースを構築するためのオープンソースJavaScriptライブラリです。

Q&A

解決済

1回答

512閲覧

reactのsetStateメソッドの引数にComputed property namesを利用したstateのプロパティ指定

moriman

総合スコア615

React.js

Reactは、アプリケーションのインターフェースを構築するためのオープンソースJavaScriptライブラリです。

0グッド

0クリップ

投稿2019/07/30 06:56

編集2019/07/31 00:32
<html> <head> <meta http-equiv="content-type" charset="utf-8"> <style> h1{ display: inline; } h2{ display: inline; } .button_inc{ width: 250px; border-radius: 0; font-size : 30pt; padding: 10px 10px; background-color: red; display: inline-block; } .button_dec{ width: 250px; border-radius: 0; font-size : 30pt; padding: 10px 10px; background-color: blue; display: inline-block; } .submit_inc{ -webkit-appearance: none; border-radius: 0; font-size : 30pt; padding: 10px 10px; background-color: pink; display: inline-block; } .submit_dec{ -webkit-appearance: none; border-radius: 0; font-size : 30pt; padding: 10px 10px; background-color: blue; display: inline-block; } .nor_css{ width:400px; background-color: yellow; font-size : 30px; padding: 10px 10px; display: inline-block; text-align: center; } .wm_css{ background-color: green; font-size: 30px; padding: 10px 10px; } .cs_css{ background-color: red; font-size: 30px; padding: 10px 10px; } .cw_css{ background-color: pink; font-size: 30px; padding: 10px 10px; } .ver{ font-size : 50px; background-color: pink; } .mess_box{ width: 400px; border: solid blue; } div#root2{ background-color: pink; font-size: 30px; width: 400px; border: solid blue; } </style> <script src="https://unpkg.com/react@16/umd/react.production.min.js"></script> <script src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script> <script src="https://unpkg.com/babel-standalone@6.26.0/babel.js"></script> </head> <body> <div id="root">root</div> <script type="text/babel"> class App extends React.Component{ constructor(props){ super(props); this.state={ aaa : 0, bbb : 0, mess : "" } } handleClickInc(e){ {/*alert(e.timeStamp+"に"+e.target.name+"を増やしました。");*/} {/*this.setState({[e.target.name]:this.state[e.target.name]+1,mess:"aaaを1増やしました。"});*/} const {name} = e.target; this.setState((prevState, props) =>({[name]:prevState[name]+1,mess:"aaaを1増やしました。"})); } handleClickDec(e){ {/*alert(e.timeStamp+"に"+e.target.name+"減らしました。");*/} {/*this.setState({[e.target.name]:prevState[e.target.name]-1,mess:"aaaを1減らしました。"});*/} const {name} = e.target; this.setState((prevState, props) =>({[name]:prevState[name]-1,mess:"aaaを1減らしました。"})); } componentDidUpdate(prevProps,prevState){ {/*document.cookie = 'aaa='+this.state.aaa;*/} {/*document.cookie = 'bbb='+this.state.bbb;*/} {/* localStorage.setItem('aaa',this.state.aaa); localStorage.setItem('bbb',this.state.bbb); */} localStorage.setItem('aaa',this.state.aaa); localStorage.setItem('bbb',this.state.bbb); } render(){ return( <div> <p>{localStorage.getItem('aaa')}</p> <p>{localStorage.getItem('bbb')}</p> <MessBox mess={this.state.mess}/> <h1>aaa</h1> <MyState nor={this.state.aaa} name="aaa" inc_mess="+1" dec_mess="-1" onClickInc={(e)=>this.handleClickInc(e)} onClickDec={(e)=>this.handleClickDec(e)}/> <h1>bbb</h1> <MyState nor={this.state.bbb} name="bbb" inc_mess="+1" dec_mess="-1" onClickInc={(e)=>this.handleClickInc(e)} onClickDec={(e)=>this.handleClickDec(e)}/> </div> ) } } class MessBox extends React.Component{ render(){ return( <div id="mess_box" class="mess_box"> {this.props.mess} </div> ) } } class MyState extends React.Component { constructor(props) { super(props); this.state = { current : new Date(), nor : 0, mess : "" }; this.onSubmitInc=this.onSubmitInc.bind(this); this.onSubmitDec=this.onSubmitDec.bind(this); } onSubmitInc(e){ console.log(e.type); console.log(e.target.name); console.log(e.currentTarget); this.props.onClickInc(e); this.setState({nor:this.state.nor+1}); document.cookie = 'nor='+this.state.nor; //alert(document.cookie); } onSubmitDec(e){ console.log(e.type); console.log(e.target.name); console.log(e.currentTarget); this.props.onClickDec(e); this.setState({nor:this.state.nor-1}); } resetLs(){ localStorage.setItem('aaa',0); localStorage.setItem('bbb',0); } render() { //document.cookie = 'nor='+this.state.nor; console.log(document.cookie); return ( <div> <h1>{this.props.rolltype}</h1> <form action="" method="POST"> <table> <tr> <td> <input className ="button_inc" type="button" name={this.props.name} value={this.props.inc_mess} onClick={this.onSubmitInc}/> {/*<input className ="button_inc" type="button" name={this.props.name} value={this.props.inc_mess} onClick={this.props.onClickInc}/>*/} </td> <td> <div className ="nor_css"><h1>{this.state.nor}</h1> 回<h2>{this.props.nor}</h2>回</div> </td> <td> <input className ="button_dec" type="button" name={this.props.name} value={this.props.dec_mess} onClick={this.onSubmitDec}/> {/*<input className ="button_dec" type="button" name={this.props.name} value={this.props.dec_mess} onClick={this.props.onClickDec}/>*/} </td> </tr> </table> </form> <hr/> <button id="reset" onClick={this.resetLs}>ローカルストレージリセット</button> </div> ) } } ReactDOM.render(<App />, document.getElementById('root')); </script> </body> </html>

reactの練習でクリックカウンタを作っています。上記コードで一応動くのですが、MyStateコンポーネントを配置する時に繰り返しを使ってやれないかと考えています。

現時点でのざっくりした流れとして
(1)Appコンポーネントのrenderメソッド内で

<MyState nor={this.state.aaa} name="aaa" inc_mess="+1" dec_mess="-1" onClickInc={(e)=>this.handleClickInc(e)} onClickDec={(e)=>this.handleClickDec(e)}/>

としてnameプロパティの"aaa"をMyStateコンポーネントに渡す。

(2)MyStateコンポーネント内で

<input className ="button_inc" type="button" name={this.props.name} value={this.props.inc_mess} onClick={this.onSubmitInc}/>

とすることで、MyStateコンポーネントのボタンが押された時にAppコンポーネントに用意したイベントハンドラhandleClickInc(e)が呼び出される。その時e.target.nameにより"aaa"が渡される。

(3)Appのstate.aaaにカウント数が1加算されてセットされる。

という流れで、一応動きます。しかしこれだとMyStateを多数配置したい時、配置したい数の分コピペすることになるので、できれば以下のようにやりたいと考えています。

(1)Appクラスのstateを例えば、

class App extends React.Component{ constructor(props){ super(props); this.state={ types : [{name:'aaa',count:0,roletype:'りんご'}, {name:'bbb',count:0,roletype:'みかん'}], mess : "" } }

のようにして、

(2)Appコンポーネントのrenderメソッド内で、

render() return( {this.state.types.map((type)=> <MyState roletype={type.roletype} nor={type.count} name={type.name} inc_mess="+1" dec_mess="-1" onClickInc={(e)=>this.handleClickInc(e)} onClickDec={(e)=>this.handleClickDec(e)}/> } )

↑のようにして繰り返しでMyStateコンポーネントをレンダーする。
これができないか考えています。

この時Appコンポーネントのイベントハンドラ(handleClickInc,handleClickDec)のsetStateの部分も書き直す必要があるのですが、ここの書き方がわかりません。computed property namesを使って動的にセットするstateを指定したいです。
handleClickIncを、

handleClickInc(e){ const {name} = e.target; this.setState((prevState, props) =>({types.[name]:prevState.types.[name]+1,mess:"aaaを1増やしました。"})); }

↑のように書くと、

Uncaught SyntaxError: Inline Babel script: Unexpected token, expected , (17:56) 15 | {/*this.setState({[e.target.name]:this.state[e.target.name]+1,mess:"aaaを1増やしました。"});*/} 16 | const {name} = e.target; > 17 | this.setState((prevState, props) =>({types.[name]:prevState.types.[name]+1,mess:"aaaを1増やしました。"})); | ^ 18 | }

↑のようなエラーが出ます。

setStateの引数でComputed property namesを使って、上記のようなstateを指定することはできるのでしょうか?
(具体的に言うと、"aaa"のボタンを押したときに、Appのstateの対応する部分(stateのプロパティ)を取得してセットしたい、ということですが、整理できてなくてすみません。整理できないのは最初のstateの設定の仕方がまずいからかとも思っています。)

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

og24715

2019/07/31 00:15

良い質問ですが、サンプルコードのインデントがガタガタで読みづらいです。後で読む方のためにも修正してみてください。
og24715

2019/07/31 00:17

"という流れで、一応動きます。しかしこれだとMyStateを多数配置したい時、配置したい数の分コピペすることになるので、できれば以下のようにやりたいと考えています。" 何をコピペするのでしょうか。
moriman

2019/07/31 00:36

Appのrenderメソッド内で<MyState ... />要素を、配置したい数の分コピペすることになるので、 できればarray.map関数を使って繰り返しレンダーしたい、という意味でした。 説明不足で申し訳ありません。
guest

回答1

0

ベストアンサー

こんにちは

ご質問にある、

(1)Appクラスのstateを例えば、
・・・
のようにして、

(2)Appコンポーネントのrenderメソッド内で、
・・・
↑のようにして繰り返しでMyStateコンポーネントをレンダーする。

のように Appthis.state.types を配列に修正したときに、それに対応して handleClickInchandleClickDec は、例えば以下のように修正すればよいかと思います。

javascript

1 handleClickInc(e) { 2 const nextTypes = this.state.types.map(type => ({ 3 ...type, 4 count: type.count + (type.name === e.target.name) 5 })) 6 this.setState({ 7 types: nextTypes, 8 mess: `${e.target.name}を1増やしました。` 9 }); 10 } 11 12 handleClickDec(e) { 13 const nextTypes = this.state.types.map(type => ({ 14 ...type, 15 count: type.count - (type.name === e.target.name) 16 })) 17 this.setState({ 18 types: nextTypes, 19 mess: `${e.target.name}を1減らしました。` 20 }); 21 }

参考までに以下のレポジトリ

に、 create-react-app で作ったSPAに、ご質問に上げられているコードを転記して作成したものを上げておきました。

上記の修正に相当するコミットは、以下です。

ただし上記の修正は、this.state.types の要素に 同じname プロパティを持つ要素がないことを前提にしています。もし、 nameが同じ要素を持つ可能性があるのであれば、this.state.types の要素となるオブジェクトに、たとえばid のようなユニークなプロパティを追加することをお勧めします。

以上、参考になれば幸いです。

追記1

一点、気になったことは、Appthis.state に、各 MyStateにrender させる可変な内容を持たせているので、 MyState のほうは状態を持つ必要があるのか?ということでした。もし今後いろいろ修正していって、MyState のほうには状態を持たせる必要がない(つまり親コンポーネントである App から渡されるpropsだけによって、render する内容などの挙動が決まる)ということであれば、MyState はFunctional Component に書き直すとよいかと思います。

追記2

App の this.state.types を、配列ではなく、 name をキーとし 回数を値とするオブジェクトにして、ご質問のタイトルにある Computed property names を使って setState するという方向で修正するとすれば、以下のようになるかと思います。

App.js

javascript

1constructor(props) { 2 super(props); 3 this.state = { 4 types: { 5 ccc: 0, 6 aaa: 0, 7 bbb: 0 8 }, 9 mess: "" 10 }; 11 }

javascript

1 handleClickInc(e) { 2 const { name } = e.target 3 const { types } = this.state 4 this.setState({ 5 types: { ...types, [name]: types[name] + 1 }, 6 mess: `${name}を1増やしました。` 7 }); 8 } 9 10 handleClickDec(e) { 11 const { name } = e.target 12 const { types } = this.state 13 this.setState({ 14 types: { ...types, [name]: types[name] - 1 }, 15 mess: `${name}を1減らしました。` 16 }); 17 }

javascript

1render() { 2 3 const sortedTypes = Object.entries(this.state.types) 4 .sort((e1, e2) => e1[0].localeCompare(e2[0])) 5 6 return ( 7 <div> 8 {sortedTypes 9 .map(([name]) => 10 <p key={name}>{name}: {localStorage.getItem(name)}</p> 11 )} 12 <MessBox mess={this.state.mess} /> 13 { 14 sortedTypes.map(([name, count]) => ( 15 <div key={name}> 16 <h1>{name}</h1> 17 <MyState 18 roletype={name} 19 nor={count} 20 name={name} 21 inc_mess="+1" 22 dec_mess="-1" 23 onClickInc={e => this.handleClickInc(e)} 24 onClickDec={e => this.handleClickDec(e)} 25 /> 26 </div> 27 )) 28 } 29 </div> 30 ); 31 }

render で、 this.state.types のエントリを配列にして、name でソートした sortedTypes を作っているのは、this.state.types をオブジェクトにすると、エントリの順番が失われるので、常に同じ順番で表示されるように、とりあえず、 name の昇順にソートしてみた、という意図です。

投稿2019/07/31 00:36

編集2019/08/01 08:36
jun68ykt

総合スコア9058

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

moriman

2019/08/01 08:03

解答を頂きましてありがとうございます。 すごいですね。こんなすっきりした書き方があるんですね。 さらに別バージョンの設定まで頂きまして本当にありがとうございました。
jun68ykt

2019/08/01 08:26

どういたしまして! setState に渡す、新しい state の内容を作るときに、 { ...x } だったり、 [ ...x ] だったりのスプレッド構文はよく使います。 特に 、プロパティ名が変数で与えられて、それだけを変えた新しいオブジェクトを作るときの { ... 変更前のオブジェクト, [変更したいプロパティ名の入っている変数] : 変更後の値 } という書き方は、よく使います。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問