Click here to Skip to main content
15,881,424 members
Please Sign up or sign in to vote.
0.00/5 (No votes)
I am working on a form that is filled by the user and should update other input fields at the same time according to the entered data. For instance, If we have 3 input fields of number 1, number 2 and their sum. How can we update each one of them as the user types? The problem with my current implementation is that it does not always work. If a field id rendered automatically and I try to change it manually, everything stops. In other words, the app is not taking into consideration the values calculated from the formulas, they are just rendered in the fields.

What I have tried:

import React, { useState } from 'react';
const Calculations = () => {

const [values,setValues]=useState({first:"",second:"",sum:""})
const [first,setFirst]=useState('')
const [second,setSecond]=useState('')
const [sum,setSum]=useState('')
const onChange=(e)=>{
    let name=e.target.name;
    let value=e.target.value;
    const newValues = {
    ...values,
    [name]: value
} 
setValues(newValues)
calcSum(newValues)
calcfirst(newValues)
calcSecond(newValues)


}
const calcSum = (newValues) => {
const { first,second} = newValues;
const newSum = parseInt(first,10)+parseInt(second,10)
setSum(newSum)
} 
const calcfirst = (newValues) => {
const { sum,second} = newValues;
const newFirst = parseInt(sum,10)-parseInt(second,10)
setFirst(newFirst)
} 
const calcSecond = (newValues) => {
const { sum,first} = newValues;
const newSecond = parseInt(sum,10)-parseInt(first,10)
setSecond(newSecond)
} 

return ( <form>
       <div style={{display:"flex",flexDirection:"column"}}>
        <label htmlFor="first">First</label>
        <input onChange={onChange} defaultValue={first} name='first' id="first" type="number"/>

        <label htmlFor="second">Second</label>
        <input onChange={onChange} defaultValue={second} name="second"  id="second" type="number"/>

        <label htmlFor="sum">Total</label>
        <input onChange={onChange} defaultValue={sum} id="sum" name="sum" type="number"/>


       </div>
    </form> );
}

export default Calculations;
Posted
Updated 19-Mar-21 3:17am

1 solution

Very good question. A practical one. But unfortunately, I've not seen a proper explanation while googling, so thought of trying to explain it myself.

Updating at least one of the state variables will rerender the whole component, (there are optimizations.. but forget about it for the moment). This is fine for many of the cases. But it's kind of forcing input components to get re-created/re-initiated, lose their status of focus. The problem becomes most visible when you have more than one input and we start using state variables (which is not necessary ).
We need not trigger a re-render for a change happening in the input field. It has it's own low-level rendering echo system. We should just provide the initial value to start with by defining defaultValue.

But we may have to listen to value changes and store them for later use as I've done with vars. I do not change vars (which is bad). Just change and use var.first. Again that's because this is a functional component. If it was class components it would be this.first.

So how do you change values input field? We keep a reference (a hook) to the input field and change the value through reference. ref.current.value=10 .


import React, { useState } from 'react';

const Calculations = () => {
  const [refs] = useState({
    first: React.createRef(),
    second: React.createRef(),
    sum: React.createRef(),
  });

  const [vars] = useState({
    first: 0,
    second: 0,
    sum: 0,
  });
  
  // Note that the above refs & vars are not really variables use to update the state (never update)
  // Just variables used to store data. like member variables (int a class component)
  
  const onChange = (e) => {
    const { name, value } = e.target;
    vars[name] = value;
    // Could've used vars, but just use refs because we keep references to all 3
    const first = parseInt(refs.first.current.value, 10);
    const second = parseInt(refs.second.current.value, 10);
    const sum = parseInt(refs.sum.current.value, 10);
    // But you need refs to update
    if (name === 'first') refs.sum.current.value = first + second;
    if (name === 'second') refs.sum.current.value = first + second;
    if (name === 'sum') refs.second.current.value = sum + second;
  };

  return (
    <form>
      <div style={{ display: 'flex', flexDirection: 'column' }}>
        <label htmlFor="first">First</label>
        <input ref={refs.first} onChange={onChange} defaultValue={vars.first} name="first" id="first" type="number" />
        <label htmlFor="second">Second</label>
        <input ref={refs.second} onChange={onChange} defaultValue={vars.second} name="second" id="second" type="number" />
        <label htmlFor="sum">Total</label>
        <input ref={refs.sum} onChange={onChange} defaultValue={vars.sum} id="sum" name="sum" type="number" />
      </div>
    </form>
  );
};

export default Calculations;


Normally you need to keep reference only to the input fields you are going to change. Because I am keeping refs to all three inputs, notice that I'm not even using [vars] just use [refs] to read the values
 
Share this answer
 
v2

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900