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,
});
const onChange = (e) => {
const { name, value } = e.target;
vars[name] = value;
const first = parseInt(refs.first.current.value, 10);
const second = parseInt(refs.second.current.value, 10);
const sum = parseInt(refs.sum.current.value, 10);
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