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>
</>
);
Related
I am having a simple form that has firstName and lastName.
<label htmlFor="firstName">First Name: </label>
<input
type="text"
className="form-control"
id="firstName"
name="firstName"
value={basicDetails.firstName}
onChange={(event) => handleInputChange(event)}
/>
<label htmlFor="lastName">Last Name: </label>
<input
type="text"
className="form-control"
id="lastName"
name="lastName"
value={basicDetails.lastName}
onChange={(event) => handleInputChange(event)}
/>
For this I am trying to add validation.
The validation rules are,
Both fields should accept only text
First name is required and should have at least 4 characters.
If Last name field has value, then it needs to be at least 3 characters.
Things I have tried to achieve this,
components/utils.js
export function isLettersOnly(string) {
return /^[a-zA-Z]+$/.test(string);
}
components/basic_details.js
const handleInputChange = (event) => {
const { name, value } = event.target;
if (!isLettersOnly(value)) {
return;
}
setValue((prev) => {
const basicDetails = { ...prev.basicDetails, [name]: value };
return { ...prev, basicDetails };
});
};
On handle input field, I am making the validation to check whether the input has value but I am unable to get the point how to catch the actual validation error and display below respective input box.
Kindly please help me to display the validation message on the respective fields.
Working example:
I suggest adding an errors property to the form data in form_context:
const [formValue, setFormValue] = useState({
basicDetails: {
firstName: '',
lastName: '',
profileSummary: '',
errors: {},
},
...
});
Add the validation to basic_details subform:
const ErrorText = ({ children }) => (
<div style={{ color: 'red' }}>{children}</div>
);
const BasicDetails = () => {
const [value, setValue] = React.useContext(FormContext);
const { basicDetails } = value;
const handleInputChange = (event) => {
const { name, value } = event.target;
if (!isLettersOnly(value)) {
setValue((value) => ({
...value,
basicDetails: {
...value.basicDetails,
errors: {
...value.basicDetails.errors,
[name]: 'Can have only letters.',
},
},
}));
return;
}
switch (name) {
case 'firstName': {
const error = value.length < 4 ? 'Length must be at least 4.' : null;
setValue((value) => ({
...value,
basicDetails: {
...value.basicDetails,
errors: {
...value.basicDetails.errors,
[name]: error,
},
},
}));
break;
}
case 'lastName': {
const error = value.length < 3 ? 'Length must be at least 3.' : null;
setValue((value) => ({
...value,
basicDetails: {
...value.basicDetails,
errors: {
...value.basicDetails.errors,
[name]: error,
},
},
}));
break;
}
default:
// ignore
}
setValue((prev) => {
const basicDetails = { ...prev.basicDetails, [name]: value };
return { ...prev, basicDetails };
});
};
return (
<>
<br />
<br />
<div className="form-group col-sm-6">
<label htmlFor="firstName">First Name: </label>
<input
type="text"
className="form-control"
id="firstName"
name="firstName"
value={basicDetails.firstName}
onChange={(event) => handleInputChange(event)}
/>
</div>
<br />
{basicDetails.errors.firstName && (
<ErrorText>{basicDetails.errors.firstName}</ErrorText>
)}
<br />
<br />
<div className="form-group col-sm-4">
<label htmlFor="lastName">Last Name: </label>
<input
type="text"
className="form-control"
id="lastName"
name="lastName"
value={basicDetails.lastName}
onChange={(event) => handleInputChange(event)}
/>
</div>
<br />
{basicDetails.errors.lastName && (
<ErrorText>{basicDetails.errors.lastName}</ErrorText>
)}
<br />
</>
);
};
Lastly, check the field values and errors to set the disabled attribute on the next button in index.js. The first !(value.basicDetails.firstName && value.basicDetails.lastName) condition handles the initial/empty values state while the second condition handles the error values.
{currentPage === 1 && (
<>
<BasicDetails />
<button
disabled={
!(
value.basicDetails.firstName && value.basicDetails.lastName
) ||
Object.values(value.basicDetails.errors).filter(Boolean).length
}
onClick={next}
>
Next
</button>
</>
)}
This pattern can be repeated for the following steps.
First you must be getting converting controlled component to uncontrolled component error in your console. For controlled component it is always preferred to use state to set value for the input. And with onChange handler you set the state. I will try to put into a single component so you would get the idea and apply your case
import React, {useState} from 'react';
import {isLettersOnly} from './components/utils'; // not sure where it is in your folder structure
const MyInputComponent = ({value, ...props}) => {
const [inputValue, setInputValue] = useState(value || ''); // input value should be empty string or undefined. null will not be accepted.
const [error, setError] = useState(null);
const handleChange = event => {
const { name, value } = event.target;
if (!isLettersOnly(value)) {
setError('Invalid Input');
}
setInputValue(value);
}
return (
<>
<input
value={inputValue}
onChange={handleChange}
{...props}
/>
{error && (
<span className={"error"}>{error}</span>
)}
</>
)
}
export default MyInputComponent;
This is a very rudimentary component. just to show the concept. You can then import this component as your input field and pass necessary props like name, className etc from parent.
import React from 'react';
import MyInputComponent from 'components/MyInputComponent';
const MyForm = (props) => {
return props.data && props.data.map(data=> (
<MyInputComponent
name="lastName"
className="form-control"
value={data.lastName}
));
}
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"
/>
);
};
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.
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>
);
}
I'm currently trying to implement a dynamic + multi-step form and was wondering how to update values of a map within a map.
For example: I have three fields "First Name", "Last Name", "Email". The name values I want to store within a key called "General" while the email value I would like to store within a key called "Contact".
Currently, I have implemented a method called onChange that is given to each field and listens for changes and stores the field and its value within the state.
function App() {
const [state, setState] = useState(false);
const onChange = (field, value) => {
console.log("Values:", value);
setState({
...state,
[field]: value
});
console.log("State:", state);
};
return (
<div className="App">
<EuiForm>
<EuiFormRow label="First Name">
<EuiFieldText
name="first"
onChange={event => onChange("firstName", event.target.value)}
/>
</EuiFormRow>
<EuiSpacer />
<EuiFormRow label="Last Name">
<EuiFieldText
name="last"
onChange={event => onChange("lastName", event.target.value)}
/>
</EuiFormRow>
<EuiSpacer />
<EuiFormRow label="Email">
<EuiFieldText
name="email"
onChange={event => onChange("email", event.target.value)}
/>
</EuiFormRow>
<EuiSpacer />
<EuiButton type="submit" fill>
Save form
</EuiButton>
</EuiForm>
</div>
);
}
What is the correct way of updating values so that the data in my state looks like this?
{
"general": {
"firstName": "ABCD",
"lastName": "EFGH"
},
"contact": {
"email": "abcd#efgh.com"
}
}
To simplify, you can define two different states and then merge them upon submit. Here is an example:
function App() {
const [general, setGeneral] = React.useState({});
const [contact, setContact] = React.useState({});
const onChange = (set, field, value) => {
set(state => ({
...state,
[field]: value
}));
};
const onSubmit = (e) => {
e.preventDefault();
console.log({
general,
contact
});
}
return (
<div className="App">
<form onSubmit={onSubmit}>
<label htmlFor="First Name">First Name
<input
name="first"
onChange={event => onChange(setGeneral, "firstName", event.target.value)}
/>
</label>
<hr />
<label htmlFor="Last Name">Last Name
<input
name="last"
onChange={event => onChange(setGeneral, "lastName", event.target.value)}
/>
</label>
<hr />
<label htmlFor="Email">Email
<input
name="email"
onChange={event => onChange(setContact, "email", event.target.value)}
/>
</label>
<hr />
<button type="submit">
Save form
</button>
</form>
</div>
);
}
ReactDOM.render(<App />, document.getElementById('root'));
<script crossorigin src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div id="root"></div>
Initially take the state as an object , like this
const [state, setState] = useState({ general :{}, contact:{});
than do some thing like this
const onChange = (field, value) => {
var temp = {...state}
if(field == 'firstName'){
temp.general.firstName = value
setState({
...state,
general:temp
});
} else if(field == 'lastName'){
temp.general.lastName= value
setState({
...state,
general:temp
});
} else if(field == 'email'){
temp.contact.email= value
setState({
...state,
contact:temp
});
}
console.log("State:", state);// this will not work as the setState is asynchronous
};
// so you can view the state like this
useEffect(() => {
console.log('State', state); // so this block of code will watch for any changes in state
}, [state]);