I am in the mid of building a shopping cart demo, I am almost done for my purposes however I faced a frustrating bug that I can't seem to get around
The box in the middle is supposed to be an input field so that if a user requests a large number of products they can easily type it down instead of incrementing.
When I type in a number it reflects correctly and produces the desired change.
However, if I typed in a number and later used the (+ / -) buttons the placeholder value doesn't seem to change.
Here is my code
<div className="prod-count">
<button className="plus" onClick={()=> onIncrement(product.id)}>+</button>
<input type="text" onChange={(e)=> handleValue(product.id, valueExtracter(e))} className="num-box" placeholder={product.quantity}/>
<button className="minus" onClick={()=> onDecrement(product.id, product.quantity)}>-</button>
</div>
and here is the onChange function
const valueExtracter = (e) => {
return parseInt(e.target.value)
}
//handle value will only run if input is a number
const handleValue = (id, value) => {
if (!isNaN(value) && value > 0){
dispatch(setQuantity(id, value))
}
}
I am pretty sure the action gets dispatched correctly and I can see it in the total values, so the (product.quantity) value changes, but the placeholder doesn't get updated
One Last thing: The desired effect gets applied once I switched placeholder to value, however, once the component is created with the value of (1) the end user cannot erase this (1) and needs to select and overwrite it
I would just trade the placeholder for value.
EDIT: 'I would trade minus button position with the increment one' its seens more user friendly
<div className="prod-count">
<button className="minus" onClick={()=> onDecrement(product.id, product.quantity)}>-</button>
<input type="text" onChange={(e)=> handleValue(product.id, valueExtracter(e))} className="num-box" value={product.quantity}/>
<button className="plus" onClick={()=> onIncrement(product.id)}>+</button>
</div>
Looks like you're missing the value prop
<input
type="text"
onChange={(e)=> handleValue(product.id, valueExtracter(e))}
className="num-box"
value={product.quantity}
placeholder={product.quantity}
/>
Also, you may not need placeholder prop with value prop there.
Had to make the value extracter accept any thing and turn to an empty string and pass it to the handle value
const valueExtracter = (e) => {
let value = parseInt(e.target.value)
if (isNaN(value)) return ''
else return value
}
//handle value will only run if input is a number
const handleValue = (id, value) => {
if (value === '' || value > 0){
dispatch(setQuantity(id, value))
}
}
switched the placeholder to value since it can accept an empty string now
<input type="text" onChange={(e)=> handleValue(product.id, valueExtracter(e))} className="num-box" value={product.quantity}/>
if the reducer catches an empty string it'll multiply it by the price which would turn the displayed price to 0 so I made a fix for that too
<div className="prod-price">
{itemPrice >= product.price ? `$ ${itemPrice.toFixed(2)}` : <span className="disabled-price">$ {product.price}</span>}
</div>
the displayed price will display in red instead if the quantity is '' since '' * a number will return 0
and finally
the total quantity and price will be updated via the reducer and if the products quantity is '' it'll turn the totals to a string so I can error check on submition and alert the user to write a quantity or delete the item
so I wrote this in the reducer to get every thing back if the user decides to use the increment value once the field has beenn cleared "" + 1 = "1" , so this was fixed by checking in the educer function
case "UPDATE_CART":
const indexToUpdate = cartCopy.findIndex(product => product.id === action.id)
// if condition checks for when the item is set to '' to reset its value to 1
if (cartCopy[indexToUpdate].quantity === ''){
let itemReadded = {
...cartCopy[indexToUpdate],
quantity: 1
}
newCart = [
...cartCopy.slice(0,indexToUpdate),
itemReadded,
...cartCopy.slice(indexToUpdate + 1)
]
return {
...state,
cart: newCart ,
totalAmount: totals(newCart).amount,
totalQty: totals(newCart).qty,
}
}
Related
I have a set of a input fields, dynamically generated by an array that are intended to be populated by default with a '?'. Once a user enters a letter into one of the inputs, the onchange event should fire and the array updates successfully, repopulating the inputs with the updated values. As of now, if I provide a value={letter} as an attribute, the inputs populate as expected, but the array that populates the inputs does not update. If I take value={letter} out, the array updates as expected, but the inputs of course, don't populate.
const [letters, setLetters] = useState(workLetters);
function inputChangeHandler(event) {
const incomingLetter = event.target.value;
const nextLetters = [...letters];
letters.forEach((letter, idx) => {
if (letters_rec_of_truth[idx] === incomingLetter) {
nextLetters[idx] = incomingLetter;
}
});
console.log("next letters is now " + nextLetters);
setLetters(nextLetters);
}
useEffect(() => {}, [letters]);
console.log("letters is now " + letters);
// console.log(evt);
return (
<div>
{letters.map((letter, idx) => {
return (
<input
type="text"
key={idx}
value={letter}
onChange={inputChangeHandler}
></input>
);
})}
</div>
);
Why instead of 'value' dont you use 'defaultValue' for the 'input' tag?
<label htmlFor="rank" className="font-bbold">Rank:
</label>
<InputNumber
id="rank"
value={singlepoints.rank}
onValueChange={(e) =>
onRankChange(e, index)}
required>
</InputNumber>
{
// (singlepoints.rank === 0 || singlepoints.rank === null ) ? () => console.log('fjdkfhdfhd') : null
// ||
(singlepoints.rank === 0 ||
singlepoints.rank === null ) ?
**(() => setInvalid(true))** &&
<small className="p-error ml-6">
Rank is required.</small>
: null
}
Hi this is not how you handle state change.
First to validate something you usually have onBlur event (it fires when the input looses focus)
Second instead of trying to running code in ternary you have to do it in the useEffect hook:
useEffect(() => {
if (singlepoints.rank === 0 ||
singlepoints.rank === null )
setInvalid(true)
}, [singlepoints])
However I can recommend use formik and yup to do the validation, once you figure it out it will make your life much and much easier in terms of form validation and change handling
You're off to a good start here.
I see that you have a label and an InputName component, and it looks like the "rank" must be required and not zero.
I want to start by making a reference to React "controlled component" which essentially is "an input form element (for example <input />) whose value is controlled by React".
The code below will give you an idea of how to rewrite your code (please note that I added a submit button to handle the conditional statement):
import { useState } from "react";
function App() {
const [singlePointsRank, setSinglePointsRank] = useState("");
const [invalid, setInvalid] = useState(false);
function handleSubmit(e) {
e.preventDefault();
if (singlePointsRank == 0) {
setInvalid(true);
} else {
//do something else if the user provides a valid answer
}
}
return (
<div className="App">
<form>
<label>Rank:</label>
<input
required
name="rank"
value={singlePointsRank}
onChange={(e) => setSinglePointsRank(e.target.value)}
/>
<button onClick={handleSubmit} type="submit">
Submit
</button>
</form>
{/*The error message below will only be displayed if invalid is set to true */}
{invalid && <p>Please provide a valid rank number.</p>}
</div>
);
}
export default App;
Note that the required property in the input element prevents the form from being submitted if it is left blank. Therefore, you do not really need to check for singlePointsRank === null.
This way React will update the state variable singlePointsRank as the user types something in it. Then, when the user clicks on the submit button, React will call the handleSubmit function to which you can add your conditional statement set your second state variable invalid to false.
Regarding ternary operator, I do not recommend using it in this case since you might want to add some extra code to your conditional statement, such as re-setting invalid back to false, removing the error message and clearing the input field to allow the user to submit another rank number.
See the example below just to give you an idea:
import { useState } from "react";
function Random2() {
const [singlePointsRank, setSinglePointsRank] = useState("");
const [invalid, setInvalid] = useState(false);
function handleSubmit(e) {
e.preventDefault();
if(singlePointsRank == 0) {
setInvalid(true);
setSinglePointsRank("");
} else {
// do something else and clear the input field and reset invalid back to false
setInvalid(false);
setSinglePointsRank("");
}
}
function handleInputChange(e) {
if(setSinglePointsRank === "") {
setInvalid(false);
}
setSinglePointsRank(e.target.value)
}
return (
<div className="App">
<form>
<label>Rank:</label>
<input
required
name="rank"
value={singlePointsRank}
onChange={handleInputChange}
/>
<button onClick={handleSubmit} type="submit">
Submit
</button>
</form>
{/*The error message below will only be displayed if invalid is set to true */}
{invalid && <p>Please provide a valid rank number.</p>}
</div>
);
}
export default Random2;
Again, since the main question here is regarding ternary operators and setting state, although I do not recommend using it in this case, you could rewrite the initial handleSubmit function as follows just to play around with your code:
function handleSubmit(e) {
e.preventDefault();
singlePointsRank == 0 ? setInvalid(true) : console.log("do something else");
}
To get a little more practice, you could rewrite
{invalid && <p>Please provide a valid rank number.</p>}
as
{invalid ? <p>Please provide a valid rank number.</p> : ""}
I am trying to build out a verification code page.
If I create an individual state for each input box, and then use the code below, it works appropriately.
<input type="number" value={inputOne} className={styles.codeInput} onChange={e => setInputOne(e.target.value}/>
However, I was trying to consolidate the state for all four of the input boxes, into one state object.
Now, when I type in a number, it moves on to the next input, but it never renders the value. In dev tools, I see the value flash like it updates, but it still stays as "value" and not "value="1"" for example.
However, if I do anything else to my code, like for example, change a p tag's text, then suddenly it updates and the inputs show the correct value.
I'm just trying to figure out what I'm doing wrong here.
Here's my current code.
import { useState } from 'react'
import styles from '../../styles/SASS/login.module.scss'
export default function Verify(params) {
const [verifCode, setVerifCode] = useState(['','','','']);
const inputHandler = (e, index) => {
// get event target value
let value = e.target.value;
// update state
let newState = verifCode;
newState[index] = value;
setVerifCode(newState);
// move focus to next input
if (e.target.nextSibling) {
e.target.nextSibling.focus()
} else {
// if at the last input, remove focus
e.target.blur();
}
}
return (
<div className={styles.verify}>
<p className={styles.title}>Verification</p>
<p className={styles.message}>Please enter the verification code we sent to your email / mobile phone.</p>
<div className={styles.form}>
<input type="number" value={verifCode[0]} className={styles.codeInput} onChange={e => inputHandler(e, 0)}/>
<input type="number" value={verifCode[1]} className={styles.codeInput} onChange={e => inputHandler(e, 1)}/>
<input type="number" value={verifCode[2]} className={styles.codeInput} onChange={e => inputHandler(e, 2)}/>
<input type="number" value={verifCode[3]} className={styles.codeInput} onChange={e => inputHandler(e, 3)}/>
</div>
<div className={styles.footer}>
<button>Verify Code</button>
</div>
</div>
)
};
I believe the problem lies in the following code
// update state
let newState = verifCode;
newState[index] = value;
setVerifCode(newState);
First line of the code just adds a pointer to the value verifCode.
You modify an element in that array, but newState is still the same variable verifCode. Even though the array elements have changed essentially it is still same variable (same reference).
Try something like:
// update state
const newState = [...verifCode]; // create a copy of the old verifCode, creating new array
newState[index] = value; // modify element
setVerifCode(newState); // set the new array to the state
I am currently trying Nuxt.js with Vuex. And I Built a simple form, where I have an email field, a password field and a button.
All my state, mutations and actions are working as they should be. But When I created a computed property just to validate the password, I always have an error with an if statement to validate length of the password.
My Vuex state looks like this:
export default () => ({
// Register Init States
registerEmail: null,
registerPassword: null,
})
My mutation:
export default {
setRegisterEmail(state, registerEmail) {
state.registerEmail = registerEmail
},
setRegisterPassword(state, registerPassword) {
state.registerPassword = registerPassword
},
}
My template:
<vs-input
:value="registerPassword"
label="Password"
primary
type="password"
:progress="getProgress"
:visible-password="hasVisiblePassword"
icon-after
#input="setRegisterPassword"
#click-icon="hasVisiblePassword = !hasVisiblePassword"
>
<template #icon>
<i v-if="!hasVisiblePassword" class="bx bx-show-alt"></i>
<i v-else class="bx bx-hide"></i>
</template>
<template v-if="getProgress == 100" #message-success
>Secure password</template
>
</vs-input>
My Computed property:
getProgress() {
let progress = 0
// at least one number
if (/\d/.test(this.$store.state.auth.registerPassword)) {
progress += 30
}
// at least one capital letter
if (/(.*[A-Z].*)/.test(this.$store.state.auth.registerPassword)) {
progress += 20
}
// at least a lowercase
if (/(.*[a-z].*)/.test(this.$store.state.auth.registerPassword)) {
progress += 25
}
// more than 8 digits
if (this.$store.state.auth.registerPassword === null) {
progress += 0
} else if (this.$store.state.auth.registerPassword.length >= 8) {
progress += 25
}
return progress
},
So because the password init state is null, there should be no progress, but as I type the password, it should to the else if and verify the number of chars.
But when I type the password, the input and my state only keeps the last letter I typed.
Imagine that I typed "overflow", my state password would only have "w". And if I remove the password validation length, my state looks like this "overflow".
Am I doing something wrong? I hope I was clear 🥺 Because I am so confused right now. 😩
The problem is being caused when you call setRegisterPassword in the template. That input event is only returning the last input character. You can add a handler to update that value properly. One option is to use a local data property as a v-model binding and then setRegisterPassword to that value in the input handler.
<vs-input
v-model="localPassword"
label="Password"
primary
type="password"
:progress="getProgress"
:visible-password="hasVisiblePassword"
icon-after
#input="handleRegisterPassword"
#click-icon="hasVisiblePassword = !hasVisiblePassword"
>
and in your
data(){
return {
localPassword:''
}
},
methods: {
handleRegisterPassword() {
this.setRegisterPassword(this.localPassword);
}
}
Note: I sam not familiar with vs-input so your :value may work the same as v-model so you may be able to leave that as value.
I have a number input within a React component, and it needs to accept numbers with a decimal point. Usually, entries will be in the fractions of a cent, like 0.0073, that kind of thing.
<div className="form-group">
<label htmlFor="rate" className="col-sm-6 control-label">Rate:</label>
<div className="col-sm-2">
<input type="number"
title="Rate"
id="rate"
className="form-control"
value={this.props.rate}
min="0.00"
step="0.001"
max="1.00"
onChange={()=>{
console.log('page rate changed');
this.props.setrate($('#rate').val());
}} />
</div>
</div>
The issue is that with every keystroke, it's resetting the rate for the app, and then putting that value into the input. So it goes like this:
User types 0, the value is set to 0, and 0 is displayed.
User types ., 0. isn't a valid number, so the input is cleared.
Can anyone think of a workaround? I know I could just use a normal input, but type="number" leads to some nice stuff in various browsers.
<input
type="text"
value={this.props.rate}
onChange={this.onAmountChange}
/>
type should be text and input value should be defined by regex.
onAmountChange = e => {
const amount = e.target.value;
if (!amount || amount.match(/^\d{1,}(\.\d{0,4})?$/)) {
this.setState(() => ({ amount }));
}
};
regex here means: start with a number and add as many as you want. then optionally end with decimal numbers up to 4 decimals.
You can do something like this
const floatRegExp = new RegExp('^[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)$')
const handleValidationOnChange = (e, v, onChange) => {
const { value } = v
if (value === '' || floatRegExp.test(value)) {
onChange(e, v)
}
}
const InputFloat = props => {
if (typeof props.onChange !== 'function') {
return <Form.Input { ...props } />
}
const { onChange, ...parentProps } = props
return <Form.Input
{ ...parentProps }
onChange={(e, v) => handleValidationOnChange(e, v, onChange)}
/>
}
Form.Input can be any Component that has a value.
You will have to later check for '', that is unavoidable.
<input type="number"
title="Rate"
id="rate"
className="form-control"
value={this.props.rate}
min="0.00"
step="0.001"
max="1.00"
presicion={2} //very important
onChange={()=>{
console.log('page rate changed');
this.props.setrate($('#rate').val());
}} />
{(text) => this.setState({ value: text.replace( /^[-+]?[0-9]+\.[^0-9]+$/, ''), })}
This will replace any input on runtime if there is any input except decimal number
I had a similar issue where I had decimal numbers in a database and reading them into React. By default, the decimal would show up with trailing zeroes when displaying in React. Trying to display without the trailing zeroes gave me the issue that you describe above. My issue may be slightly different from what you are looking for, but I think it would help others. What I had to do:
Pull the data from the database with the desired formatting. For example: instead of select mynumber from mytable I did select (TRIM(mynumber)+0) as mynumber from mytable. This automatically removes the trailing zeroes.
I then read these values into the React JS script and set in state. These become the value of the input fields.
Then rather than preventing users from entering a non-number value I simply change the background color of the input field to red if the value is not a number.
if (isNaN(mynumber)) {
bgcolor = "#fdd" //Add this to your input style
}
I found the red background above good enough for my purposes but you can also implement an additional check when the user clicks the save button.
I've tried your code and wasn't really affected by your issue, the value is indeed empty when you type the last dot, but the input is not reset.
The thing I changed from your implementation is that I get the input value from the onChange event rather than use jQuery.
<input onChange={e => this.props.setrate(e.target.value)} />
But I doubt your issue comes from that though.
What you could do is not to call your setrate function when you detect an ending dot in your string. Your state will not be modified until the user types a valid number, so one keystroke after the 0..
<input onChange={e => {
const str = e.target.value
if (str.charAt(str.length - 1) === '.') { return }
this.props.setrate(str)
}} />