I have multiple inputs where user fill these inputs with numbers. I need a way to sum up the values of these inputs when user types in any input.
My Code
const [values,set_values] = useState({
sales:'',
bank_deposit:'',
supply:'',
expenses:''
})
const values_handler = (e) => {
let name= e.target.name;
let value= e.target.value;
values[name]=value;
set_values(values)
// Calling the method to sum the value
calc_total(value)
}
const [total,set_total]=useState(0);
const calc_total = (value) => {
total +=value;
set_total(total)
}
<input type='number' onChange={value_handler} name='sales' />
<input type='number' onChange={value_handler} name='bank_deposit' />
<input type='number' onChange={value_handler} name='supply' />
<input type='number' onChange={value_handler} name='expenses' />
Problem
The problem with this is that the values are summed up each time the value of an input got a chane, so that if user enters 15, it sums 1 then 5 since the mehtod is executed when a change occurred on the input's value.
you must not mutate the state values.
state updates are async.
you need not use the value of each onChange instead use the state values to update total value
Assuming you want to sum sales, bank_deposit, supply and expeses values, you can get them from states do it like below
const [values,set_values] = useState({
sales:'',
bank_deposit:'',
supply:'',
expenses:''
})
const values_handler = (e) => {
let name= e.target.name;
let value= e.target.value;
const newValues = {
...values,
[name]: value
}
set_values(newValues)
// Calling the method to sum the value
calc_total(newValues)
}
const [total,set_total]=useState(0);
const calc_total = (newValues) => {
const { sales, bank_deposit, expenses, supply} = newValues;
const newTotal = parseInt(sales) + parseInt(bank_deposit) + parseInt(expenses) + parseInt(supply)
setTotal(newTotal)
}
<input type='number' onChange={value_handler} name='sales' />
<input type='number' onChange={value_handler} name='bank_deposit' />
<input type='number' onChange={value_handler} name='supply' />
<input type='number' onChange={value_handler} name='expenses' />
try this:
const [values,set_values] = useState({
sales:'',
bank_deposit:'',
supply:'',
expenses:''
})
const values_handler = (e) => {
let name= e.target.name;
let value= e.target.value;
set_values({...values , [name]: value})
// Calling the method to sum the value
calc_total(values)
}
const [total,set_total]=useState(0);
const calc_total = (values) => {
aux = 0
for (var key in values){
aux += values[key]
}
set_total(aux)
}
<input type='number' onChange={value_handler} name='sales' />
<input type='number' onChange={value_handler} name='bank_deposit' />
<input type='number' onChange={value_handler} name='supply' />
<input type='number' onChange={value_handler} name='expenses' />
Remarks:
Using Chris G. recommendations in the comments
const Solution = () => {
const [input_values, set_inputvalues] = useState({
sales: 0,
bank_deposit: 0,
supply: 0,
expenses: 0
});
const [total, set_total] = useState(0);
useEffect(() => {
const arrValues = Object.values(input_values);
const inputTotals = arrValues.reduce((accum, curr) => (accum += curr), 0);
set_total(inputTotals);
}, [input_values]);
const changeValues = ({ name, value }) => {
set_inputvalues({ ...input_values, [name]: parseInt(value) });
};
return (
<div>
<h1>{total}</h1>
<input
type="number"
onChange={({ target }) => changeValues(target)}
name="sales"
/>
<input
type="number"
onChange={({ target }) => changeValues(target)}
name="bank_deposit"
/>
<input
type="number"
onChange={({ target }) => changeValues(target)}
name="supply"
/>
<input
type="number"
onChange={({ target }) => changeValues(target)}
name="expenses"
/>
</div>
);
};
To some up some key gotchas which other's have mentioned:
Don't alter state directly, use a set_xxx to update state.
You set type="number" so default state has to be numeric, i.e. sales: 0
Remember to parse values to int if numeric.
Related
How to get all input values by click to count and multiply them all? Without useref, just somehow add result of all event.target.values to quantity?
const [quantity, setQuantity] = useState(0);
function handleChange(event) {
setQuantity(event.target.value);
}
function countCups() {
setQuantity(hours * l * degrees * capacity);
}
return(
<>
<input type="number" placeholder="hours" onChange={handleChange}/>
<input type="number" placeholder="l" onChange={handleChange}/>
<input type="number" placeholder="degrees" onChange={handleChange}/>
<input type="number" placeholder="capacity" onChange={handleChange}/>
<button id="countButton" onClick={count}>COUNT</button>
<span>{quantity}</span>
</>
);
Currently whenever you change an input value it's replacing the existing value in state. Ideally you want to store each input state separately. Now you can do that with separate states or you can use an object as in the example below. You can destructure the input name and value from the event.target (i.e. the changed element), and use those values to update the each object key/value.
When you want to calculate the total quantity that doesn't necessarily need to be stored in state. You can call a function that multiplies the object values together, and use the returned value in the JSX.
const { Fragment, useState } = React;
function Example() {
// Initialise the inputs state with an object
const [inputs, setInputs] = useState({});
// Destructure the name and value from the
// changed element, and then use those to update
// the state
function handleChange(event) {
const { name, value } = event.target;
setInputs(prev => {
return { ...prev, [name]: Number(value) };
});
}
// Returns a value by multiplying all the state
// values together
function getQuantity() {
let quantity = 1;
for (const key in inputs) {
const val = inputs[key];
if (val) quantity *= val;
}
return quantity > 1 ? quantity : null;
}
// Each input now has a name attribute
// and is "controlled" by including the value
// from the state
return (
<Fragment>
<input
type="number"
name="hours"
placeholder="hours"
value={inputs.hours || ''}
onChange={handleChange}
/>
<input
type="number"
name="l"
placeholder="l"
value={inputs.l || ''}
onChange={handleChange}
/>
<input
type="number"
name="degrees"
placeholder="degrees"
value={inputs.degrees || ''}
onChange={handleChange}
/>
<input
type="number"
name="capacity"
placeholder="capacity"
value={inputs.capacity || ''}
onChange={handleChange}
/>
<div>Quantity: {getQuantity()}</div>
</Fragment>
);
}
ReactDOM.render(
<Example />,
document.getElementById('react')
);
<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>
You can do this by adding a name to your inputs which you can use as a key in a state object to save your input states
const [state, setState] = useState({});
const [quantity, setQuantity] = useState(0);
function handleChange(event) {
const name = event.target.name;
const value = event.target.value;
setState((prevState) => {
return {
...prevState,
[name]: value,
};
});
}
function countCups() {
setQuantity(state.hours * state.l * state.degrees * state.capacity);
}
return (
<>
<input
type="number"
name="hours"
placeholder="hours"
value={state.hours ?? ""}
onChange={handleChange}
/>
<input
type="number"
name="l"
placeholder="l"
value={state.l ?? ""}
onChange={handleChange}
/>
<input
type="number"
name="degrees"
placeholder="degrees"
value={state.degrees ?? ""}
onChange={handleChange}
/>
<input
type="number"
name="capacity"
placeholder="capacity"
value={state.capacity ?? ""}
onChange={handleChange}
/>
<button id="countButton" onClick={count}>
COUNT
</button>
<span>{quantity}</span>
</>
);
I am trying to build a quiz application where I want to generate no of Question input fields based on admin inputs.
So suppose the admin enters 10 questions for the quiz.
Then I am rendering the form inside for loop for 10 Questions and their answers respectively.
The problem I am facing is I am not able to get all values from input fields.
Below is my demo code:
import { useState } from "react";
const MyComponent = () => {
const [inputs, setInputs] = useState({});
const handleChange = (e) =>
setInputs((prevState) => ({
...prevState,
[e.target.name]: e.target.value
}));
const finalData = (e) => {
e.preventDefault();
console.log("data", inputs);
};
function buildRows() {
const arr = [];
for (let i = 1; i <= 3; i++) {
arr.push(
<div key={i} id={i}>
<input name="Question" onChange={handleChange} />
<input name="option1" onChange={handleChange} />
<input name="option2" onChange={handleChange} />
<input name="option3" onChange={handleChange} />
<input name="option4" onChange={handleChange} />
</div>
);
}
return arr;
}
return (
<>
{buildRows()}
<button
onClick={(e) => finalData(e)}
variant="contained"
className="button-left"
sx={{ marginRight: 3.5 }}
>
Submit Quiz Questions
</button>
</>
);
};
export default MyComponent;
You could use the id (or any other unique property, a unique name would probably be preferred) you're giving your div and build your object with that as an array index like so:
const handleChange = (e) => {
const parent = e.currentTarget.parentNode;
const id = parent.id;
setInputs((prevState) => ({
...prevState,
[id]: {
...prevState[id],
[e.target.name]: e.target.value
}
}));
};
This produces an object like this:
{
"1":{
"Question":"1",
"option1":"2",
"option2":"3",
"option3":"4",
"option4":"5"
},
"2":{
"Question":"6",
"option1":"7",
"option2":"8",
"option3":"9",
"option4":"11"
},
"3":{
"Question":"22",
"option1":"33",
"option2":"44",
"option3":"55",
"option4":"66"
}
}
Basically, I want a little box with only one digit, it "works" fine until you try to put multiple '0', then in the box, multiple 0000 persist.
The code
const InputBox = () => {
const [value, setValue] = useState(0);
const handleChange = el => {
const newValue = el.target.value;
console.log(newValue);
const lastNumber = parseInt(
newValue.toString().slice(-1),
);
console.log(lastNumber);
setValue(lastNumber);
};
return (
<input
type='number'
pattern='[0-9]'
min='0'
max='9'
id='numberInput'
maxLength={1}
minLength={1}
value={value}
onInput={handleChange}
// onChange={handleChange}
className='inputBox'
/>
);
};
export default InputBox;
How to make it so I have only one digit at any time?
Don't need to parse to int or to string, just slice and set value.
minLength and maxLength doesn't work in <input type="number">.
const InputBox = () => {
const [value, setValue] = useState(0);
const handleChange = (el) => {
let newValue = el.target.value;
if (newValue.length > 1) {
newValue = newValue.slice(0, 1);
}
setValue(newValue);
};
return (
<input
type="number"
pattern="[0-9]"
min="0"
max="9"
id="numberInput"
value={value}
onInput={handleChange}
className="inputBox"
/>
);
};
This could work out
const handleChange = el => {
const newValue = el.target.value;
const lastNumber = newValue.replace(/[^0-9]/g,'')
setValue(lastNumber);
};
return (
<input
type="text" maxlength="1"
value={value}
onInput={handleChange}
/>
);
If you don't mind the type coercion to string of your state (you can always Number(value) when you need a number type for computations) then you can simply slice off the last char of the value and if it ends up being a falsey ('', 0, null, undefined, NaN) value fallback to 0 to allow coercion back to "0" when assigning to the value prop of the input. Also, the minLength|maxLength|pattern attributes only work when doing field validation in a form.
const handleChange = (el) => {
const { value } = el.target;
setValue(value.slice(-1) || 0);
};
const InputBox = () => {
const [value, setValue] = useState(0);
const handleChange = (el) => {
const { value } = el.target;
setValue(value.slice(-1) || 0);
};
return (
<input
type="number"
min="0"
max="9"
id="numberInput"
value={value}
onChange={handleChange}
className="inputBox"
/>
);
};
on react docs forms section there is the following example using class components:
class Reservation extends React.Component {
constructor(props) {
super(props);
this.state = {
isGoing: true,
numberOfGuests: 2
};
this.handleInputChange = this.handleInputChange.bind(this);
}
handleInputChange(event) {
const target = event.target;
const value = target.type === 'checkbox' ? target.checked : target.value;
const name = target.name;
this.setState({
[name]: value
});
}
render() {
return (
<form>
<label>
Is going:
<input
name="isGoing"
type="checkbox"
checked={this.state.isGoing}
onChange={this.handleInputChange} />
</label>
<br />
<label>
Number of guests:
<input
name="numberOfGuests"
type="number"
value={this.state.numberOfGuests}
onChange={this.handleInputChange} />
</label>
</form>
);
}
}
Considering Hooks can only be called either in a React function component or a custom React Hook function is there a way of doing it using hooks instead?
you can clean up #adam 's final solution a bit by not using the useCallback hook, and instead simply using the useState hook as a controlled component.
const MyComponent = () => {
const [inputs, setInputs] = useState({});
const handleChange = e => setInputs(prevState => ({ ...prevState, [e.target.name]: e.target.value }));
return (
<>
<input name="field1" value={inputs.field1 || ''} onChange={handleChange} />
<input name="field2" value={inputs.field2 || ''} onChange={handleChange} />
</>
)
}
example
const MyComponent = () => {
const [inputs,setInputs] = useState({});
return (
<>
<input key="field1" name="field1" onChange={({target}) => setInputs(state => ({...state,field1:target.value}))} value={inputs.field1}/>
<input key="field2" name="field2" onChange={({target}) => setInputs(state => ({...state,field2:target.value}))} value={inputs.field2}/>
</>
)
}
you can pass in initial values like this:
const MyComponent = (initialValues = {}) => {
const [inputs,setInputs] = useState(initialValues);
...
}
EDIT: A nice short onChange according to #hamidreza's comment
const MyComponent = (initialValues = {}) => {
const [inputs,setInputs] = useState(initialValues);
const onChangeHandler = useCallback(
({target:{name,value}}) => setInputs(state => ({ ...state, [name]:value }), [])
);
return (
<>
<input key="field1" name="field1" onChange={onChangeHandler} value={inputs.field1}/>
<input key="field2" name="field2" onChange={onChangeHandler} value={inputs.field2}/>
</>
)
}
etc, etc, etc
Maybe, on the last example onChangeForField('...') will be triggered on each render, so maybe you have to write onChange={()=>onChangeForField('...')} or if you want the event to get passed onChange={(e)=>onChangeForField('...', e)}
I was looking for the same answer,but i was finding difficulty to understand the previous solutions,so i tried in my own way ,and i found a solution.
const [inputs,setInputs] = useState({
'field1':'',
'field2':'',
});
const handleChange = (e) => {
const name = e.target.name; //it is the name of that input
const value = e.target.value; //value of that input
setInputs((prev) => {
prev[name] = value;//changing the updated value to the previous state
return prev;
});
};
return (
<>
<input key="field1" name="field1" onChange={handleChange} value={inputs.field1}/>
<input key="field2" name="field2" onChange={handleChange} value={inputs.field2}/>
</>
adding to Adam's answer and for those who are looking towards typescript solution,
interface MyIType {
field1: string;
...
}
//Partial from typescript to make properties optional
interface MyFormType extends Partial<MyIType> {}
const [inputs,setInputs] = useState<MyFormType>(initialValues);
const onChangeForField = useCallback(({target}) =>
setInputs(_state => {
return {
..._state,
[target.name]: target.value,
};
}),
[]
);
If you were like me, having multiple inputs on multiple pages using the same input id/name/key, try value={data.xxx || ''} .
Full code:
const [data, setData] = useState<any>({});
const handleValueChanges = e => {
setData({
...data,
[e.target.name]: e.target.value,
});
};
<InputText (using prime react)
id="firstName"
name="firstName"
value={data.firstName || ''}
onChange={handleUpdate}
/>
As of v6 you can use .forEach(), Please refer to the migrate guide
[{name: "firstName", value: "Safwat" }, {name: "lastName", value: "Fathi", }].forEach(({name, value}) => setValue(name, value));
I'd like to keep the two digits after a number ie 2.89 or 2.00. Google brought me to this answer to use .toFixed(2).
While that works great, it does not work well when entered input values:
const [ value, setValue] = useState()
const onChange = (value) => {
const float = parseFloat(value)
setValue(float.toFixed(2))
}
<input
type="number"
value={value}
onChange={({ target }) => onChange(target.value)}
/>
If I should type, say, "300", the input value stops at "3.00". I have to move the cursor before "3" to type "300". What's the best way to do this?
I expect the value to always show .33, .00 etc while having the ability to "free type". As I type this question, I feel I need to use onBlur to convert the value to .toFixed and not while typing?
You can use onBlur and add some checks in while setting value
export default function App() {
const [value, setValue] = useState('');
const onChange = (v) => {
if (!Number.isNaN(v)) {
setValue(v);
}
};
return (
<div className="App">
<input
type="number"
value={value}
step="1"
onChange={({ target }) => onChange(target.value)}
onBlur={e => setValue(Number(value).toFixed(2))}
/>
</div>
);
}
I would not try to set the decimal places on the number on the onChange but make an onBlur handler.
const TodoApp = ( ) => {
const [ value, setValue] = React.useState('');
const onBlur = (e) => {
const float = parseFloat(e.target.value)
setValue(float.toFixed(2))
}
return (
<input
type="number"
value={value}
onChange={(e) => setValue(e.target.value)}
onBlur={onBlur}
/>
);
}
Hi dude read this https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/number
And try using step propety, i recommended
<input
type="number"
value={value}
step="1"
onChange={({ target }) => onChange(target.value)}
/>
and try if it works
export const InputElement = () => {
const [value, setValue] = React.useState(0);
const fixInt = (v) => {
setValue(Number(v).toFixed(2));
};
return (
<div className="App">
<input
type="number"
value={value}
step="1"
onChange={({ target }) => fixInt(target.value)}
/>
</div>
);
}