stateにタグを配列形式で追加した後、map関数でrenderしようとしました
json内のデータをもとに配列にタグを追加しようとしたところ、エラーが出てつまりました
発生している問題・エラーメッセージ
TypeError: this.state.drawnTag.map is not a function
該当のソースコード
state = { drawnTag: [] } getInfo = () =>{ var ref_Data = require('../users_mirror.json'); var components_Data = ref_Data.users[this.props.account_Name].components; Object.keys(components_Data).forEach(function(key){ if(components_Data[key]){ this.setState({ drawnTag: this.state.drawnTag.push(<Comp />) }); } }.bind(this)); console.log(this.state.drawnTag); } componentWillMount(){ this.getInfo(); console.log(this.state.drawnTag); } render(){ return( <div> { this.state.drawnTag.map((item, i) => <React.Fragment key={i}><div>{item}</div></React.Fragment>) } </div> );
配列内に収めたタグを順番にrenderして出力したいです、なにが間違っているのでしょうか
気になる質問をクリップする
クリップした質問は、後からいつでもMYページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
回答1件
0
ベストアンサー
こんにちは
setState
している箇所を、以下のように修正してみると、いかがでしょうか?
修正前:
javascript
1this.setState({ 2 drawnTag: this.state.drawnTag.push(<Comp />) 3});
修正後:
javascript
1this.setState({ 2 drawnTag: [...this.state.drawnTag, <Comp />] 3});
修正前の何が問題かというと、push
の戻り値は、要素追加後の配列ではない点です。
Array.prototype.push() の戻り値のところに以下の記載があります。
戻り値
メソッドが呼び出されたオブジェクトの新しい length プロパティ。
以上参考になれば幸いです。
追記1
componentWillMount は React 16.3から非推奨メソッドになったので、v16.3以降を使用の際は、替わりに componentDidMount を使うことをお勧めします。
追記2
Clock
の render
で発生している "Nothing was returned from render."
というエラーは、以下の修正でとりあえず発生しなくなると思います。
修正前:
jsx
1 render() { //デジタル時計のデザインをpropsで管理できるようにした."view=''"で"vertical"で水平に"horizontal"で縦並びに 2 if (this.props.view === "horizontal") { 3 return ( 4 <div 5 style={{}} 6 className="clock_flame" 7 > 8 <span id="clock_hour"> 9 {this.state.hours} 10 </span> 11 <span id="clock_minute"> 12 {this.state.minutes} 13 </span> 14 </div> 15 ) 16 } else if (this.props.view === "vertical") { 17 return ( 18 <div 19 style={{}} 20 className="clock_flame_vertical" 21 > 22 <div id="clock_hour_vertical" className="vertical"> 23 {this.state.hours} 24 </div> 25 <div id="clock_minute_vertical" className="vertical"> 26 {this.state.minutes} 27 </div> 28 </div> 29 ); 30 } 31 }
修正方法ですが、render()
の終わりの }
の直前に
javascript
1return <div>{this.state.hours}{this.state.minutes}</div>;
を追加して、 以下のようにします。
修正後:
jsx
1 render() { //デジタル時計のデザインをpropsで管理できるようにした."view=''"で"vertical"で水平に"horizontal"で縦並びに 2 if (this.props.view === "horizontal") { 3 return ( 4 <div 5 style={{}} 6 className="clock_flame" 7 > 8 <span id="clock_hour"> 9 {this.state.hours} 10 </span> 11 <span id="clock_minute"> 12 {this.state.minutes} 13 </span> 14 </div> 15 ) 16 } else if (this.props.view === "vertical") { 17 return ( 18 <div 19 style={{}} 20 className="clock_flame_vertical" 21 > 22 <div id="clock_hour_vertical" className="vertical"> 23 {this.state.hours} 24 </div> 25 <div id="clock_minute_vertical" className="vertical"> 26 {this.state.minutes} 27 </div> 28 </div> 29 ); 30 } 31 32 return <div>{this.state.hours}{this.state.minutes}</div>; // この行を追加 33 }
このように修正すると、Clock
の props view
が与えられなかったり、与えられてもhorizontal
またはvertical
のいずれでもない場合に、とりあえず{this.state.hours}
と{this.state.minutes}
を並べて、 classNameやstyleを指定しない単なる <div>
で囲んだものを返すようになり、Clock を使う側で <Clock />
のように view
を指定し忘れても、
"Nothing was returned from render."
は出なくなると思います。
その上で Clock を使う側では <Clock view="horizontal" />
または <Clock view="vertical" />
というように、適切な値のview
をpropsで渡すようにすればよいかと思います。逆に、表示したときにスタイルがあたらずに時と分が表示されたら、それは view
の値が適切にprops経由で渡せていないと気がつくことができます。
追記3
ご質問にあるコードの以下の部分で、 forEach
の中で setState しているのが怪しいです。
修正前:
getInfo = () =>{ var ref_Data = require('../users_mirror.json'); var components_Data = ref_Data.users[this.props.account_Name].components; Object.keys(components_Data).forEach(function(key){ if(components_Data[key]){ this.setState({ drawnTag: this.state.drawnTag.push(<Comp />) }); } }.bind(this)); }
これだと、components_Data
に複数タグ分のデータがあったときに、setStateが複数回呼ばれてしまいます。ですので、上記を以下のように修正してみるといかがでしょうか? (こんどはpush
で大丈夫です。)
修正後:
javascript
1 getInfo = () =>{ 2 var ref_Data = require('../users_mirror.json'); 3 var components_Data = ref_Data.users[this.props.account_Name].components; 4 var drawnTag = []; 5 Object.keys(components_Data).forEach(function(key){ 6 if(components_Data[key]){ 7 drawnTag.push(<Comp />); 8 } 9 }); 10 this.setState({ drawnTag }); 11 };
すなわち、 forEach
のループに入る前に、 drawnTag
というローカル変数で空配列の配列を用意しておき、 forEach
のループでこの配列に <Comp />
を追加していきます。そして、forEach
を抜けた後に、
this.setState({ drawnTag });
で、state に反映させるようにします。こうすることでgetInfo
が呼ばれたときに setState
が1回だけ実行されるようになります。
上記の趣旨の修正をお手元のコードにしてみると意図通り動くかもしれません。
それと、 console.log(this.state.drawnTag);
というデバッグログは、以下のように render の中で行うのがよいです。一度、他の箇所にあるconsole.log(this.state.drawnTag);
をコメントアウトして、以下の箇所にだけ入れてみることをお勧めします。
javascript
1 render() { 2 console.log(this.state.drawnTag); 3 return ( 4 <div> 5 {this.state.drawnTag.map((item, i) => 6 <React.Fragment key={i}> 7 <div>{item}</div> 8 </React.Fragment>) 9 } 10 </div> 11 ); 12 }
追記4
コメントから頂きました、
hfidk 2019/01/17 18:13
余計な質問すいません,追加するタグの名前をオブジェクトのキー名にすることってできませんか?
とのご質問への回答です。これは以下の質問と同じ要望かと思います。
- stackoverflow: Create an instance of a React class from a string
上記への回答、
にある
So you simply need to find a way to map between the string "Home" and the component class Home. A simple object will work as a basic registry, and you can build from there if you need more features.
javascript
1var components = { 2 "Home": Home, 3 "Other": OtherComponent 4}; 5 6var Component = components[this.props.template];
を参考にすればよいです。以下簡単なサンプルです。
3つのタグコンポーネント FooTag
, BarTag
, BazTag
があります。App
のprops tag
に "foo"
,"bar"
,"baz"
のいずれかが渡ってくる想定で、これらの文字列から表示対象のタグコンポーネントを変数 Tag
に入れて、これをJSX形式で書いて表示するという例です。
jsx
1class FooTag extends React.Component { 2 render() { 3 return <span>This is a FooTag.</span>; 4 } 5} 6 7class BarTag extends React.Component { 8 render() { 9 return <span>This is a BarTag.</span>; 10 } 11} 12 13class BazTag extends React.Component { 14 render() { 15 return <span>This is a BazTag.</span>; 16 } 17} 18 19const tagComponents = { 20 foo: FooTag, 21 bar: BarTag, 22 baz: BazTag, 23}; 24 25class App extends React.Component { 26 render() { 27 const Tag = tagComponents[this.props.tag] || FooTag; 28 return ( 29 <div className="app"> 30 <Tag /> 31 </div> 32 ); 33 } 34} 35 36ReactDOM.render(<App tag="bar" />, document.querySelector("#app"));
- 動作確認用サンプル: https://jsfiddle.net/jun68ykt/0zc3L7ke/7/
キーとなる文字列とコンポーネントをマップするオブジェクト tagComponents
を作るというのが、ひと手間かかりますが、いったんルールを決めて作ってしまえば、追加していくのはそれほど大変ではありません。私自身、時々、業務でも使う小技です。
肝は、
javascript
1const Tag = tagComponents[this.props.tag] || FooTag;
と、Tag
のような、大文字で始まる変数名に代入することです。こうすると <Tag />
と書けますが、これを
javascript
1const tag = tagComponents[this.props.tag] || FooTag;
として <tag />
と書いても、意図したようには表示されません。
投稿2019/01/15 01:54
編集2019/01/18 06:25総合スコア9058
あなたの回答
tips
太字
斜体
打ち消し線
見出し
引用テキストの挿入
コードの挿入
リンクの挿入
リストの挿入
番号リストの挿入
表の挿入
水平線の挿入
プレビュー
質問の解決につながる回答をしましょう。 サンプルコードなど、より具体的な説明があると質問者の理解の助けになります。 また、読む側のことを考えた、分かりやすい文章を心がけましょう。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2019/01/15 13:58
2019/01/15 14:41 編集
2019/01/15 18:56
2019/01/16 02:08
2019/01/16 17:31
2019/01/17 02:19
2019/01/17 08:54
2019/01/17 09:04
2019/01/17 09:13
2019/01/17 10:13
2019/01/17 12:21