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

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

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

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

Q&A

解決済

2回答

688閲覧

Reactのイベントハンドラーについて

lisateratail

総合スコア7

React.js

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

0グッド

0クリップ

投稿2020/01/08 02:29

Reactの学習をしており、コンポネントを作成したのですが想定どおりの動きをさせるために何をすれば良いのかアドバイスいただければと存じます。

コンポネント↓

JS

1class Body extends React.Component{ 2 constructor(props){ 3 super(props); 4 this.state = { 5 open: false, 6 open2: false 7 } 8 this.handleClick = this.handleClick.bind(this); 9 } 10 handleClick(){ 11 this.setState(state => ({ 12 open: !state.open 13 })); 14 } 15 16 render(){ 17 const answer1 = "15時までのご注文で当日配送いたしますので、最短で翌日にお届けとなります。また、発送が完了しだい追跡番号をお知らせいたしますので、発送後の配達状況をお調べいただくことができます。"; 18 const answer2 = "しろねこメイル便でのお届けとなります。他の配送業者様や配送方法をご指定いただくことはできません。"; 19 const answer3 = "このページからのご注文に限り、送料無料でお届けいたします!キャンペーンコードの入力が必要となりますので、入力漏れがないかお確かめください。"; 20 const answer4 = "大変申し訳ございませんが、返品は承っておりません。ただし、発送の際に商品チェックを実施しておりますが、万が一ご注文いただいた商品と異なっていたり、破損や不具合などが見つかりました場合は、返品・交換対応をさせていただきます。"; 21 return( 22 <div> 23 <div class="title"> 24 <p>ご注文に際してよくあるご質問</p> 25 </div> 26 <div class="questions"> 27 <label onClick={this.handleClick}> 28 <p>注文してからどのくらいで届きますか?▼ 29 <p> 30 { 31 this.state.open === true ? answer1 : "" 32 } 33 </p> 34 </p> 35 </label> 36 </div> 37 <div class="questions"> 38 <label onClick={this.handleClick}> 39 <p>配送業者を指定できますか? 40 <p> 41 { 42 this.state.open === true ? answer2 : "" 43 } 44 </p> 45 </p> 46 </label> 47 </div> 48 <div class="questions"> 49 <label onClick={this.handleClick}> 50 <p>送料はかかりますか? 51 <p> 52 { 53 this.state.open === true ? answer3 : "" 54 } 55 </p> 56 </p> 57 </label> 58 </div> 59 <div class="questions"> 60 <label onClick={this.handleClick}> 61 <p>返品は可能ですか? 62 <p> 63 { 64 this.state.open === true ? answer4 : "" 65 } 66 </p> 67 </p> 68 </label> 69 </div> 70 </div> 71 ); 72 } 73}

ブラウザには↓のように表示される
イメージ説明

それぞれの質問をクリックした時に、対応する答えが出てほしいのですが、
現状のコードだと一つの質問をクリックすると、下記のように全ての質問の答えが表示されてしまいます。
イメージ説明

クリックした質問に対応する答えのみ表示されるようにするには、どのような方法がありますでしょうか?
アドバイスいただけますと嬉しいです。

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

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

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

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

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

guest

回答2

0

こんにちは、lisateratailさん

質問、拝見しました。

Reactのstate管理がうまくいっていないようです。

Reactの基本的な考え方としては判定が必要な項目分、stateを持つ必要があるです。

今回に関しては、answerが4つありますので、定義するstateも4つ必要です。

class Body extends React.Component{ constructor(props){ super(props); this.state = { openAns1: false, openAns2: false, openAns3: false, openAns4: false, } } handleClick(targetName){ console.log('targetName', targetName); console.log('this.state', this.state); this.setState({ [targetName]: !this.state[targetName] }); } render(){ const answer1 = "15時までのご注文で当日配送いたしますので、最短で翌日にお届けとなります。また、発送が完了しだい追跡番号をお知らせいたしますので、発送後の配達状況をお調べいただくことができます。"; const answer2 = "しろねこメイル便でのお届けとなります。他の配送業者様や配送方法をご指定いただくことはできません。"; const answer3 = "このページからのご注文に限り、送料無料でお届けいたします!キャンペーンコードの入力が必要となりますので、入力漏れがないかお確かめください。"; const answer4 = "大変申し訳ございませんが、返品は承っておりません。ただし、発送の際に商品チェックを実施しておりますが、万が一ご注文いただいた商品と異なっていたり、破損や不具合などが見つかりました場合は、返品・交換対応をさせていただきます。"; return( <div> <div className="title"> <p>ご注文に際してよくあるご質問</p> </div> <div className="questions"> <label onClick={this.handleClick.bind(this, 'openAns1')}> <p>注文してからどのくらいで届きますか?▼ <p> {this.state.openAns1 && answer1} </p> </p> </label> </div> <div className="questions"> <label onClick={this.handleClick.bind(this, 'openAns2')}> <p>配送業者を指定できますか? <p> {this.state.openAns2 && answer2} </p> </p> </label> </div> <div className="questions"> <label onClick={this.handleClick.bind(this, 'openAns3')}> <p>送料はかかりますか? <p> {this.state.openAns3 && answer3} </p> </p> </label> </div> <div className="questions"> <label onClick={this.handleClick.bind(this, 'openAns4')}> <p>返品は可能ですか? <p> {this.state.openAns4 && answer4} </p> </p> </label> </div> </div> ); } }

handleClickについては、それぞれanswer用のhandlerを4つ定義してもよいのですが、必要なのはstateの情報のみなので、state名をbindで引数として渡して、共通のhandlerでstateを更新するようにしています。

[targetName]: !this.state[targetName]の記法は、こちらが参考になると思いますので、ご参照ください。
ES2015以降のJavaScriptでObjectのkeyに変数を使う

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

投稿2020/01/08 03:15

gentamura

総合スコア406

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

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

lisateratail

2020/01/08 11:06

ご回答いただき、ありがとうございます! わかりやすい文章で解説いただき大変参考になりました。 リンクの共有もいただき、ありがとうございます。 コード写経して理解していこうと思います。
guest

0

ベストアンサー

こんにちは

ご質問の本題である、

クリックした質問に対応する答えのみ表示されるようにするには、どのような方法がありますでしょうか?

への回答含め、ご質問に挙げられているコードのリファクタ案の一例を、以下の手順で説明します。

  1. 質問と回答の組をReactコンポーネントの外に持つようにする。
  2. Bodyコンポーネントのstateを修正
  3. ひとつの質問とその回答を表示するdivを子コンポーネントにする。
  4. クリックハンドラを修正
  5. 上記の 1.〜 4.を組み合わせて動作するようにBodyを修正

1. 質問と回答の組をReactコンポーネントの外に持つようにする。

ご質問にある現状のコードでは、質問と回答のテキストが、 Bodyコンポーネントのrenderで返すJSXの中に書かれていますが、今後、質問の追加、削除、修正があったときに、手を加える箇所を分かりやすくするために、質問と回答の組を、コンポーネントの外で作ります。その一例として、以下のようなJSONテキストで持つようにします。

json

1[ 2 { 3 "id": 1, 4 "text": "注文してからどのくらいで届きますか?", 5 "answer": "15時までのご注文で当日配送いたしますので、最短で翌日にお届けとなります。また、発送が完了しだい追跡番号をお知らせいたしますので、発送後の配達状況をお調べいただくことができます。" 6 }, 7 { 8 "id": 2, 9 "text": "配送業者を指定できますか?", 10 "answer": "しろねこメイル便でのお届けとなります。他の配送業者様や配送方法をご指定いただくことはできません。" 11 }, 12 { 13 "id": 3, 14 "text": "送料はかかりますか?", 15 "answer": "このページからのご注文に限り、送料無料でお届けいたします!キャンペーンコードの入力が必要となりますので、入力漏れがないかお確かめください。" 16 }, 17 { 18 "id": 4, 19 "text": "返品は可能ですか?", 20 "answer": "大変申し訳ございませんが、返品は承っておりません。ただし、発送の際に商品チェックを実施しておりますが、万が一ご注文いただいた商品と異なっていたり、破損や不具合などが見つかりました場合は、返品・交換対応をさせていただきます。" 21 } 22]

上記のように、ひとつの質問と回答の組、およびユニークなidを持つオブジェクトの配列としておきます。このJSONを、Bodyのコンストラクターでパースします。

2. Bodyコンポーネントのstateを修正

  • Body コンポーネントの state では、開いている(=回答が表示されている)質問の id を配列で持つようにして、プロパティ名を openIds とします。
  • this.state.openIdsが例えば [2, 4] のとき、id が 2と4の質問が開いている状態であることを表します。
  • 初期表示ではすべての質問は閉じていることにするならば、this.state.openIds の初期値は、以下のように、空の配列です。

javascript

1this.state = { 2 openIds: [] 3};

3. ひとつの質問とその回答を表示するdivを子コンポーネントにする。

ひとつの質問とその回答を表示するdivを子コンポーネントに切り出します。これはstateを持つ必要がないので、functional componentにします。

javascript

1const Question = ({ text, answer, isOpen, onClick }) => ( 2 <div class="question"> 3 <label onClick={onClick}> 4 <p>{text} 5 {isOpen && <p>{answer}</p>} 6 </p> 7 </label> 8 </div> 9);

上記で、回答の表示部分を

jsx

1{isOpen && <p>{answer}</p>}

としていますが、ご質問にあるJSXに沿うと

jsx

1<p>{isOpen ? answer : ''}</p>

というものになります。ただし、これだと、 isOpenがfalseのときにも、内容が空の <p></p>ができてしまいますので、これを避けるために

jsx

1{isOpen && <p>{answer}</p>}

としています。

4. クリックハンドラを修正

Body の handleClick を以下のように修正します。

  • 引数として、質問のidを受け取り、
  • その idthis.state.openIds にあれば、this.state.openIdsから削除する、
  • または、その idthis.state.openIds に無ければ、this.state.openIdsに追加する
  • ように、this.state を更新

このような handleClick は以下のように書けます。

javascript

1 handleClick(id) { 2 let { openIds } = this.state; 3 openIds = openIds.includes(id) ? openIds.filter(e => e !== id) : [...openIds, id]; 4 this.setState({ openIds }); 5 }

5. 上記の 1.〜 4.を組み合わせて動作するようにBodyを修正

上記の 1.〜 4.を組み合わせて動作するようにBodyを修正したものが以下です。

jsx

1const QUESTIONS_JSON = `[ 2 { 3 "id": 1, 4 "text": "注文してからどのくらいで届きますか?", 5 "answer": "15時までのご注文で当日配送いたしますので、最短で翌日にお届けとなります。また、発送が完了しだい追跡番号をお知らせいたしますので、発送後の配達状況をお調べいただくことができます。" 6 }, 7 { 8 "id": 2, 9 "text": "配送業者を指定できますか?", 10 "answer": "しろねこメイル便でのお届けとなります。他の配送業者様や配送方法をご指定いただくことはできません。" 11 }, 12 { 13 "id": 3, 14 "text": "送料はかかりますか?", 15 "answer": "このページからのご注文に限り、送料無料でお届けいたします!キャンペーンコードの入力が必要となりますので、入力漏れがないかお確かめください。" 16 }, 17 { 18 "id": 4, 19 "text": "返品は可能ですか?", 20 "answer": "大変申し訳ございませんが、返品は承っておりません。ただし、発送の際に商品チェックを実施しておりますが、万が一ご注文いただいた商品と異なっていたり、破損や不具合などが見つかりました場合は、返品・交換対応をさせていただきます。" 21 } 22]`; 23 24const Question = ({ text, answer, isOpen, onClick }) => ( 25 <div class="question"> 26 <label onClick={onClick}> 27 <p>{text} 28 {isOpen && <p>{answer}</p>} 29 </p> 30 </label> 31 </div> 32); 33 34 35class Body extends React.Component { 36 37 constructor(props){ 38 super(props); 39 this.questions = JSON.parse(QUESTIONS_JSON); 40 this.state = { 41 openIds: [] 42 }; 43 this.handleClick = this.handleClick.bind(this); 44 } 45 46 handleClick(id) { 47 let { openIds } = this.state; 48 openIds = openIds.includes(id) ? openIds.filter(e => e !== id) : [...openIds, id]; 49 this.setState({ openIds }); 50 } 51 52 render(){ 53 return( 54 <div> 55 <div class="title"> 56 <p>ご注文に際してよくあるご質問</p> 57 </div> 58 {this.questions.map(q => ( 59 <Question 60 key={q.id} 61 text={q.text} 62 answer={q.answer} 63 isOpen={this.state.openIds.includes(q.id)} 64 onClick={() => { this.handleClick(q.id); }} 65 /> 66 ))} 67 </div> 68 ); 69 } 70}

以下にて、上記のBodyをマウントして動作確認できます。(スタイルは適当に作りました。)

補足

ご質問のコードのJSXに以下のテキスト

注文してからどのくらいで届きますか?▼

がありましたが、これの末尾にある ▼ は、回答が開いているときに表示するのか、閉じているときに表示するのか不明だったので、上記のコードには反映していませんが、 Questionコンポーネントのprop isOpen が true かfalseかによって、この▼のような、開閉状態を示す文字列を追加するorしない、という処理を追加すればよいかと思います。

補足2

Body の this.state.openIds を配列ではなく、オブジェクトで持つことも考えられます。この場合、コンストラクターで以下のように空オブジェクトで初期化します。

javascript

1 this.state = { 2 openIds: {} 3 };

上記に合わせると、handleClick は以下のようになります。

javascript

1 handleClick(id) { 2 this.setState({ 3 openIds: { 4 ...this.state.openIds, 5 [id]: !this.state.openIds[id] 6 } 7 }); 8 }

さらに、render の中で Question に渡す isOpen を以下のように修正します。

jsx

1isOpen={!!this.state.openIds[q.id]}

上記で!!は無くても動きますが、 boolean を渡すことを明示するために付けています。

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

投稿2020/01/08 05:21

編集2020/01/08 07:13
jun68ykt

総合スコア9058

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

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

lisateratail

2020/01/08 10:58

ご回答いただきありがとうございます! ゆくゆく応用編として質問と回答の組をJSONで管理できるようなコンポネントにしたいと思っておりましたので大変参考になりました。 全体的に新しいテクニックかつわかりやすい解説をいただき勉強になりました。 「4. クリックハンドラを修正」の「 let { openIds } = this.state;」について、解説いただけないでしょうか? https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Destructuring_assignmenthttps://qiita.com/takahiro_itazuri/items/1016494c2e3b815cbe6d を参考にしてみたのですが理解できず。 宜しくお願いいたします。
jun68ykt

2020/01/08 11:37 編集

こんにちは MDNの以下 >https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment を参照されたとのことで、読むべきドキュメントは正しいです。 let { openIds } = this.state; は、以下と同じです。 let openIds = this.state.openIds; 他の例を挙げると、たとえば const obj = { x:1, y: 2, z: 3 }; というオブジェクト obj があったときに、 obj のプロパティである x, y ,z と同じ名前の変数 x, y, z  に、 各々のプロパティの値である1, 2, 3 を入れたいときに、分割代入が使えるようになる以前だと var x = obj.x; var y = obj.y; var z = obj.z; とやっていましたが、ES2015から分割代入ができるようになってからは、以下の一行 const { x, y, z } = obj; で済むようになりました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問