0

The following code has a button to add inputs and another button to add all of the values in the inputs assuming they are numbers. The last line is to display the sum of everything. However, for some reason it only gets the value of the last input. It also does not reset. I thought having the setGrade(0) would do this but it just keeps adding the last number without resetting.

I would just like to know why this is the case with the following code. The id for the input fields are just the number starting from 1.

function Start(){
 const [rows, setRows] = useState([]);
 const [inputNum,setNum] = useState(1);
 const [totalGrade, setGrade] = useState(0);


 const addInput = () =>{
   setNum(inputNum+1);
   setRows(rows.concat(<Inputs key={rows.length} label={inputNum.toString()}></Inputs>));
 }

 const addGrade = () =>{
   setGrade(0);
   for(let i =0;i<rows.length;i++){
     setGrade(parseInt(document.getElementById((i+1).toString()).value,10) +totalGrade)
   }
 }

 

 return(
   <div>
     <h1>Calculate your GPA!</h1>
     {rows}
     <button class="button2"onClick={addInput}>Add Input</button>
     <button class="button2"onClick={addGrade}>Compute Grade</button>
     <h2>Grade: {totalGrade}</h2>
   </div>
 );
}
1
  • You shouldn't be putting components in an array in state unless you really know what you're doing. Instead, render components based on an array in state. Commented Sep 30, 2021 at 20:21

1 Answer 1

2
  1. You shouldn't be mixing native element methods like getElementById in React code.

  2. Add an onChange directly onto the input elements.

  3. Create a new state (an object) that maintains a record of each change to an input (identified by key/id) where the value is the new value of the input.

  4. Don't set state in a loop - it's bad practice.

Here's how I've approached it (I've had to simplify the example as I don't have access to the Inputs component.)

const { useEffect, useState } = React;

  function Example() {

    const [inputNum, setInputNum] = useState(0);

    // The new state which maintains all the input values
    const [inputData, setInputData] = useState({});
    const [totalGrade, setTotalGrade] = useState(0);

    // `addInput` is now only responsible
    // for updating the number of rows
    function addInput() {
      setInputNum(inputNum + 1);
    }

    // NEW FUNCTION: it handles the update of the
    // `inputData` state. It grabs the id and value from
    // the input, and then updates the state with that
    // new information
    function handleChange(e) {
      const { id, value } = e.target;

      // Take the previous state (object), and update it
      // by spreading (copying) out the previous object,
      // and adding a new property with the id as key
      // and the value as the value.
      setInputData(prev => ({ ...prev, [id]: value }));
    }

    // `sumGrade` - I renamed this - grabs the Object.values
    // of the inputData state and then creates a sum of all
    // of those values using `reduce`. It then, finally, sets
    // the `totalGrade` state.
    function sumGrade() {
      const values = Object.values(inputData);
      const result = values.reduce((acc, c) => {
        return acc + +c;
      }, 0);
      setTotalGrade(result);
    }

    // NEW FUNCTION: this builds an array of new inputs
    // which can be used in the JSX
    function buildRows() {
      const arr = [];
      for (let i = 0; i < inputNum; i++) {
        arr.push(<input onChange={handleChange} type="number" key={i} id={i} value={inputData[i]}/>);
      }
      return arr;
    }

    return (
     <div>
       <h1>Calculate your GPA!</h1>
       {buildRows()}
       <button class="button2"onClick={addInput}>Add Input</button>
       <button class="button2"onClick={sumGrade}>Compute Grade</button>
       <h2>Grade: {totalGrade}</h2>
     </div>
    );
  }

// Render it
ReactDOM.render(
  <Example />,
  document.getElementById("react")
);
input { display: block; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="react"></div>

Sign up to request clarification or add additional context in comments.

4 Comments

Thanks for the tip! I understand why it would be better that why. I dont fully understand the handleChange function but I just copied it directly and applied it to my code but it leads to being unable to type anything in the input fields when I run it. Is the ...prev actual syntax or supposed to mean something.
I added some additional notes to the handleClick function. I also update the <input> element which sets the value based on the current state, so that might work for you in your code if you play around with it a little. @AlfonsoLima
Oh I got it now it works thanks so much learned a lot!
Glad to help @AlfonsoLima

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.