こんにちは
ご質問の本題である、
クリックした質問に対応する答えのみ表示されるようにするには、どのような方法がありますでしょうか?
への回答含め、ご質問に挙げられているコードのリファクタ案の一例を、以下の手順で説明します。
質問と回答の組をReactコンポーネントの外に持つようにする。
Bodyコンポーネントのstateを修正
ひとつの質問とその回答を表示するdivを子コンポーネントにする。
クリックハンドラを修正
上記の 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
1 this . state = {
2 openIds : [ ]
3 } ;
3. ひとつの質問とその回答を表示するdivを子コンポーネントにする。
ひとつの質問とその回答を表示するdivを子コンポーネントに切り出します。これはstateを持つ必要がないので、functional componentにします。
javascript
1 const 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
を受け取り、
その id
が this.state.openIds
にあれば、this.state.openIds
から削除する、
または、その id
が this.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
1 const 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
24 const 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
35 class 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
1 isOpen = { ! ! this . state . openIds [ q . id ] }
上記で!!
は無くても動きますが、 boolean を渡すことを明示するために付けています。
以上、参考になれば幸いです。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2020/01/08 11:06