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

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

新規登録して質問してみよう
ただいま回答率
85.35%
JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

React.js

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

Q&A

解決済

1回答

2171閲覧

Reactで、子コンポーネントの数が増減する場合に、子コンポーネントの数値の合計値を集計したい

退会済みユーザー

退会済みユーザー

総合スコア0

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

React.js

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

0グッド

0クリップ

投稿2021/11/28 19:33

前提・実現したいこと

  • Reactで、縦×横×高さで直方体の体積を求める表計算を作っています。

各行毎に個別の直方体を計算しており、行数は増やしたり減らしたりできます。

  • 行毎の体積計算はできていますが、体積の合計を求める所をどうしたらいいか分からないです。

イメージ説明

該当のソースコード

  • 現状のコードは、下記codesandboxにあげています。

https://codesandbox.io/s/20211124-tablemultiple-47vp9?file=/src/App.js

  • 念のため、本質問の最後にコード全文を載せています。

  • 現状の実装では、親の方で、下記のように行数(直方体の数)のみを管理しています。

const [numberOfRectangulars, setNumberOfRectangulars] = useState(1);
  • 子コンポーネントの方で、Row関数を定義し、個別の直方体の数値管理、計算、表示を担っています。

  • 行数が、例えば2行とか固定されていれば処理できると思うのですが、増減する場合、どのようにしたら集計できるのか分かりません。

試したこと

  • 下記のような案を考えて実装しようとしました。

1.直方体のクラスを作る。

React

1class Rectangular { 2 constructor(vertical, horizontal, height) { 3 this.vertical = vertical; 4 this.horizontal = horizontal; 5 this.height = height; 6 } 7 }

2.番号リスト行追加時に子コンポーネントであるRowにpropsで渡してインスタンスを作る
(親側)

React

1<Row Rectangular={Rectangular} />

(子側)

React

1const [Rectangular] = props 2const rectangular = new Rectangular(0,0,0)

3.入力した結果を親に戻す。

React

1[rectanculars, setRectangulars] = useState([])

4.親の方でrectangularsをforループ等して、合計を計算する。

  • しかし、そもそも、Rectangularというクラスを子コンポーネントに渡すことができず、②の時点で失敗しています。

Invalid attempt to destructure non-iterable instance.
In order to be iterable, non-array objects must have a Symbol.iterator method.

何かお気づきの方、ご教示頂けたら幸いです。
よろしくお願いいたします。

コード全文

React

1import { useState, useEffect } from "react"; 2 3export default function App() { 4 const [numberOfRectangulars, setNumberOfRectangulars] = useState(1); 5 const [total, setTotal] = useState(0); 6 const decreaseCubes = () => { 7 if (numberOfRectangulars === 0) return; 8 setNumberOfRectangulars(numberOfRectangulars - 1); 9 }; 10 const increaseCubes = () => { 11 setNumberOfRectangulars(numberOfRectangulars + 1); 12 }; 13 14 return ( 15 <div className="App"> 16 <table className="table-auto my-10"> 17 <thead> 18 <tr> 19 <th className="px-4 py-2">縦</th> 20 <th className="px-4 py-2">横</th> 21 <th className="px-4 py-2">高さ</th> 22 <th className="px-4 py-2">体積</th> 23 </tr> 24 </thead> 25 <tbody> 26 {[...(Array(numberOfRectangulars) || 0)].map((key) => ( 27 <Row key={key} /> 28 ))} 29 </tbody> 30 <tfoot> 31 <tr> 32 <th colSpan="3">計</th> 33 <td className="text-right">{total}</td> 34 </tr> 35 </tfoot> 36 </table> 37 <button 38 className="rounded border-2 p-1 w-20 mx-2" 39 type="button" 40 onClick={decreaseCubes} 41 > 42 減らす 43 </button> 44 <button 45 className="rounded border-2 p-1 w-20 mx-2" 46 type="button" 47 onClick={increaseCubes} 48 > 49 増やす 50 </button> 51 </div> 52 ); 53} 54const Row = () => { 55 const [vertical, setVertical] = useState(0); 56 const [horizontal, setHorizontal] = useState(0); 57 const [height, setHeight] = useState(0); 58 const onChangeVertical = (event) => { 59 setVertical(event.target.value); 60 }; 61 const onChangeHorizontal = (event) => { 62 setHorizontal(event.target.value); 63 }; 64 const onChangeHeight = (event) => { 65 setHeight(event.target.value); 66 }; 67 const volume = vertical * horizontal * height; 68 return ( 69 <tr> 70 <td> 71 <input 72 type="number" 73 value={vertical} 74 onChange={onChangeVertical} 75 className="rounded border-2 p-1 w-20 text-right" 76 /> 77 </td> 78 <td> 79 <input 80 type="number" 81 value={horizontal} 82 onChange={onChangeHorizontal} 83 className="rounded border-2 p-1 w-20 text-right" 84 /> 85 </td> 86 <td> 87 <input 88 type="number" 89 value={height} 90 onChange={onChangeHeight} 91 className="rounded border-2 p-1 w-20 text-right" 92 /> 93 </td> 94 <td className="text-right">{volume}</td> 95 </tr> 96 ); 97}; 98

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

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

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

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

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

guest

回答1

0

ベストアンサー

修正案を回答します。見直した点は、App で

javascript

1const [numberOfRectangulars, setNumberOfRectangulars] = useState(1); 2const [total, setTotal] = useState(0);

として、行の個数と体積の合計をstateとして持っていることです。これを修正して、以下

javascript

1const [volumes, setVolumes] = useState([0]);

のような、各行の体積を持つ想定の配列をstateにするとよいかと思います。初期表示では、入力できる行は1行で、体積の初期値は0なので、[0] を初期状態とする volumesを useState で作成します。

以下は、このvolumes を元にした修正箇所です。

diff

1 import "./styles.css"; 2-import { useState, useEffect } from "react"; 3-// import { Cube } from "./components/Cube"; 4+import { useState, useCallback } from "react"; 5 6 export default function App() { 7- const [numberOfCubes, setNumberOfCubes] = useState(1); 8- const [total, setTotal] = useState(0); 9+ const [volumes, setVolumes] = useState([0]); 10+ 11 const decreaseCubes = () => { 12- if (numberOfCubes === 0) return; 13- setNumberOfCubes(numberOfCubes - 1); 14+ if (volumes.length === 0) return; 15+ setVolumes(volumes.slice(0, volumes.length - 1)); 16 }; 17+ 18 const increaseCubes = () => { 19- setNumberOfCubes(numberOfCubes + 1); 20+ setVolumes([...volumes, 0]); 21 }; 22 23+ const handleChangeVolumes = useCallback( 24+ (selectedIndex, volume) => { 25+ setVolumes(volumes.map((v, i) => (i === selectedIndex ? volume : v))); 26+ }, 27+ [volumes] 28+ ); 29+ 30 return ( 31 <div className="App"> 32 <table className="table-auto my-10">

diff

1 </tr> 2 </thead> 3 <tbody> 4- {[...(Array(numberOfCubes) || 0)].map((key) => ( 5- <Row key={key} /> 6+ {volumes.map((volume, i) => ( 7+ <Row 8+ key={`row-${i}`} 9+ volume={volume} 10+ onChangeVolume={(volume) => handleChangeVolumes(i, volume)} 11+ /> 12 ))} 13 </tbody> 14 <tfoot> 15 <tr> 16 <th colSpan="3">計</th> 17- <td className="text-right">{total}</td> 18+ <td className="text-right"> 19+ {volumes.reduce((total, v) => total + v, 0)} 20+ </td> 21 </tr> 22 </tfoot> 23 </table>

diff

1 </div> 2 ); 3 } 4-const Row = () => { 5+const Row = ({ volume, onChangeVolume }) => { 6 const [vertical, setVertical] = useState(0); 7 const [horizontal, setHorizontal] = useState(0); 8 const [height, setHeight] = useState(0); 9+ 10 const onChangeVertical = (event) => { 11- setVertical(event.target.value); 12+ const updatedEdge = event.target.valueAsNumber; 13+ setVertical(updatedEdge); 14+ onChangeVolume(updatedEdge * horizontal * height); 15 }; 16+ 17 const onChangeHorizontal = (event) => { 18- setHorizontal(event.target.value); 19+ const updatedEdge = event.target.valueAsNumber; 20+ setHorizontal(updatedEdge); 21+ onChangeVolume(vertical * updatedEdge * height); 22 }; 23+ 24 const onChangeHeight = (event) => { 25- setHeight(event.target.value); 26+ const updatedEdge = event.target.valueAsNumber; 27+ setHeight(updatedEdge); 28+ onChangeVolume(vertical * horizontal * updatedEdge); 29 }; 30- const volume = vertical * horizontal * height; 31+ 32 return ( 33 <tr> 34 <td> 35

別案

stateを見直すことは同じですが、各直方体の3辺の情報自体を、Appのstate として持つというのもありかと思います。たとえば、以下のようなクラスを作り、これのインスタンスの配列 cubes を stateに持たせます。

javascript

1class Cube { 2 constructor(otherCube) { 3 this.vertical = otherCube ? otherCube.vertical : 0; 4 this.horizontal = otherCube ? otherCube.horizontal : 0; 5 this.height = otherCube ? otherCube.height : 0; 6 } 7 8 get volume() { 9 return this.vertical * this.horizontal * this.height; 10 } 11}

上記のクラスCube は、ご質問に添付のコードに含まれていたclass Rectangular と同様のものです。以下はこのクラスを使った場合の修正箇所です。

diff

1 import "./styles.css"; 2-import { useState, useEffect } from "react"; 3-// import { Cube } from "./components/Cube"; 4+import { useState, useCallback } from "react"; 5+ 6+class Cube { 7+ constructor(otherCube) { 8+ this.vertical = otherCube ? otherCube.vertical : 0; 9+ this.horizontal = otherCube ? otherCube.horizontal : 0; 10+ this.height = otherCube ? otherCube.height : 0; 11+ } 12+ 13+ get volume() { 14+ return this.vertical * this.horizontal * this.height; 15+ } 16+} 17 18 export default function App() { 19- const [numberOfCubes, setNumberOfCubes] = useState(1); 20- const [total, setTotal] = useState(0); 21+ const [cubes, setCubes] = useState([new Cube()]); 22+ 23 const decreaseCubes = () => { 24- if (numberOfCubes === 0) return; 25- setNumberOfCubes(numberOfCubes - 1); 26+ if (cubes.length === 0) return; 27+ setCubes(cubes.slice(0, cubes.length - 1)); 28 }; 29+ 30 const increaseCubes = () => { 31- setNumberOfCubes(numberOfCubes + 1); 32+ setCubes([...cubes, new Cube()]); 33 }; 34 35+ const handleChangeCube = useCallback( 36+ (selectedIndex, name, size) => { 37+ setCubes( 38+ cubes.map((cube, i) => { 39+ if (i === selectedIndex) { 40+ const newCube = new Cube(cube); 41+ newCube[name] = size; 42+ return newCube; 43+ } 44+ return cube; 45+ }) 46+ ); 47+ }, 48+ [cubes] 49+ ); 50+ 51 return ( 52 <div className="App"> 53 <table className="table-auto my-10"> 54

diff

1 </tr> 2 </thead> 3 <tbody> 4- {[...(Array(numberOfCubes) || 0)].map((key) => ( 5- <Row key={key} /> 6+ {cubes.map((cube, i) => ( 7+ <Row 8+ key={`cube-${i}`} 9+ cube={cube} 10+ onChange={(name, size) => handleChangeCube(i, name, size)} 11+ /> 12 ))} 13 </tbody> 14 <tfoot> 15 <tr> 16 <th colSpan="3">計</th> 17- <td className="text-right">{total}</td> 18+ <td className="text-right"> 19+ {cubes.reduce((total, cube) => total + cube.volume, 0)} 20+ </td> 21 </tr> 22 </tfoot> 23 </table>

diff

1 </div> 2 ); 3 } 4-const Row = () => { 5- const [vertical, setVertical] = useState(0); 6- const [horizontal, setHorizontal] = useState(0); 7- const [height, setHeight] = useState(0); 8- const onChangeVertical = (event) => { 9- setVertical(event.target.value); 10- }; 11- const onChangeHorizontal = (event) => { 12- setHorizontal(event.target.value); 13- }; 14- const onChangeHeight = (event) => { 15- setHeight(event.target.value); 16- }; 17- const volume = vertical * horizontal * height; 18+const Row = ({ cube, onChange }) => { 19+ const handleChange = useCallback( 20+ (name, size) => { 21+ onChange(name, size); 22+ }, 23+ [onChange] 24+ ); 25+ 26 return ( 27 <tr> 28- <td> 29- <input 30- type="number" 31- value={vertical} 32- onChange={onChangeVertical} 33- className="rounded border-2 p-1 w-20 text-right" 34- /> 35- </td> 36- <td> 37- <input 38- type="number" 39- value={horizontal} 40- onChange={onChangeHorizontal} 41- className="rounded border-2 p-1 w-20 text-right" 42- /> 43- </td> 44- <td> 45- <input 46- type="number" 47- value={height} 48- onChange={onChangeHeight} 49- className="rounded border-2 p-1 w-20 text-right" 50- /> 51- </td> 52- <td className="text-right">{volume}</td> 53+ {["vertical", "horizontal", "height"].map((name) => ( 54+ <td key={name}> 55+ <input 56+ type="number" 57+ value={cube[name]} 58+ onChange={(e) => handleChange(name, e.target.valueAsNumber)} 59+ className="rounded border-2 p-1 w-20 text-right" 60+ /> 61+ </td> 62+ ))} 63+ <td className="text-right">{cube.volume}</td> 64 </tr> 65 ); 66 // useEffect(()=>{ 67

投稿2021/11/28 21:04

編集2021/11/28 23:59
退会済みユーザー

退会済みユーザー

総合スコア0

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

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

退会済みユーザー

退会済みユーザー

2021/11/29 09:51

大変ご丁寧にご回答頂き、大変感謝しております。 コードを読んで、勉強させて頂きますm(__)m
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.35%

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

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

質問する

関連した質問