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

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

ただいまの
回答率

88.65%

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

解決済

回答 1

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 785

moriman

score 101

<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の設定の仕方がまずいからかとも思っています。)

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

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

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

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • og24715

    2019/07/31 09:15

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

    キャンセル

  • og24715

    2019/07/31 09:17

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

    キャンセル

  • moriman

    2019/07/31 09:36

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

    キャンセル

回答 1

checkベストアンサー

0

こんにちは

ご質問にある、

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

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

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

  handleClickInc(e) {
    const nextTypes = this.state.types.map(type => ({
      ...type,
      count: type.count + (type.name === e.target.name)
    }))
    this.setState({
      types: nextTypes,
      mess: `${e.target.name}を1増やしました。`
    });
  }

  handleClickDec(e) {
    const nextTypes = this.state.types.map(type => ({
      ...type,
      count: type.count - (type.name === e.target.name)
    }))
    this.setState({
      types: nextTypes,
      mess: `${e.target.name}を1減らしました。`
    });
  }

 

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

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

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

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

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

追記1

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

追記2

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

App.js

constructor(props) {
    super(props);
    this.state = {
      types: {
        ccc: 0,
        aaa: 0,
        bbb: 0
      },
      mess: ""
    };
  }
  handleClickInc(e) {
    const { name } = e.target
    const { types } = this.state
    this.setState({
      types: { ...types, [name]: types[name] + 1 },
      mess: `${name}を1増やしました。`
    });
  }

  handleClickDec(e) {
    const { name } = e.target
    const { types } = this.state
    this.setState({
      types: { ...types, [name]: types[name] - 1 },
      mess: `${name}を1減らしました。`
    });
  }
render() {

    const sortedTypes = Object.entries(this.state.types)
                              .sort((e1, e2) => e1[0].localeCompare(e2[0]))

    return (
      <div>
        {sortedTypes
          .map(([name]) =>
            <p key={name}>{name}: {localStorage.getItem(name)}</p>
          )}
        <MessBox mess={this.state.mess} />
        {
          sortedTypes.map(([name, count]) => (
            <div key={name}>
              <h1>{name}</h1>
              <MyState
                roletype={name}
                nor={count}
                name={name}
                inc_mess="+1"
                dec_mess="-1"
                onClickInc={e => this.handleClickInc(e)}
                onClickDec={e => this.handleClickDec(e)}
              />
            </div>
          ))
        }
      </div>
    );
  }

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

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/08/01 17:03

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

    キャンセル

  • 2019/08/01 17:26

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

    { ... 変更前のオブジェクト, [変更したいプロパティ名の入っている変数] : 変更後の値 }

    という書き方は、よく使います。

    キャンセル

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

  • ただいまの回答率 88.65%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る