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?
Related
I have a Form component with a title input and a dynamic number of ingredient inputs (the user can add and remove them with corresponding buttons). Most of the form functions properly. However, I receive a warning that I have passed a value prop to a form field without an onChange handler for the ingredient input element(s).
In render() for my Form component, this part should dynamically generate inputs (EDIT: by mapping from an array of ingredients in my Form's state):
{this.state.ingredients.map((element, i) => {
return (
<div key={i}>
<input
type='text'
placeholder='new ingredient'
value={element || ''}
onChange={this.handleIngredientChange.bind(this)}
/>
</div>
);
})}
I thought these inputs would connect to my handleIngredientChange method to update the state of the ith ingredient name when the user changes the input value:
handleIngredientChange(i, event) {
let ingredients = [...this.state.ingredients];
ingredients[i] = event.target.value;
this.setState({ingredients});
}
And that this method would appropriately allow react to control each input element.
I seem to be misunderstanding this and/or .bind(), because I clearly assigned onChange={this.handleIngredientChange.bind(this)} to the ingredient input element in my map function. When the user types in one of these inputs, we get `TypeError: undefined is not an object (evaluating 'event.target').
Perhaps the root of the problem is that when the user types in the input, handleIngredientChange is not correctly set up to get the user's desired input value, but I cannot see where the error is.
I have looked at a lot of similar questions and have tried to implement their answers to no avail. Could anyone point me in the right direction on why I have not handled the onChange event properly? I'd be more than happy to explain my intentions further if you need me to. Many thanks in advance.
Full code:
export default class Form extends React.Component {
constructor(props) {
super(props);
this.state = {
title: '',
ingredients: [],
};
}
handleTitleChange(event) {
let title = this.state.title;
title = event.target.value;
this.setState({title});
}
handleIngredientChange(i, event) {
let ingredients = [...this.state.ingredients];
ingredients[i] = event.target.value;
this.setState({ingredients});
}
removeClick(i) {
let ingredients = [...this.state.ingredients];
ingredients.splice(i, 1);
this.setState({ingredients});
}
addClick() {
let ingredients = [...this.state.ingredients];
ingredients.push('');
this.setState({ingredients});
}
handleSubmit(event) {
console.log('submit request logged')
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<input
type='text'
placeholder='recipe title'
value={this.state.title || ''}
onChange={this.handleTitleChange.bind(this)}
/>
{this.state.ingredients.map((element, i) => {
return (
<div key={i}>
<input
type='text'
placeholder='new ingredient'
value={element || ''}
onChange={this.handleIngredientChange.bind(this)}
/>
</div>
);
})}
<input
type='button'
value='add'
onClick={this.addClick.bind(this)}
/>
<input
type='Submit'
value='save'
onClick={this.handleSubmit.bind(this)}
/>
</form>
)
}
}
I'm not sure if this helps
try change
<input
type="text"
placeholder="new ingredient"
value={element || ""}
id={i}
onChange={this.handleIngredientChange.bind(this)}
/>
and then alter
handleIngredientChange(event) {
let ingredients = [...this.state.ingredients];
ingredients[event.target.id] = event.target.value;
this.setState({ ingredients });
}
https://codesandbox.io/s/react-playground-forked-yf7rpm
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 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,
}
}
I have a string , in certain places I need to insert input tags with values. Everything displays fine , but I can't delete or edit values in input. What is wrong with that input?
editModalText() {
let modalMessage="Hello, my name is /# Ann #/. I'm working for /# IStaff #/, could you please call me back"
return (
<div>
{modalMessage
.split("/")
.map((text, idx) =>
text.includes("#") ? this.replaceCharacter(idx, text) : text,
)}
</div>
)
}
replaceCharacter(idx, text) {
let formattedText = text.replace(/#/g, " ")
return (
<input
key={idx}
value={formattedText}
onChange={e => this.setState({input:e.target.value})}
/>
)
}
replace value={formattedText} with defaultValue={formattedText}
this way input will be editable. it will show default value on first render and as you type you'll store that value in your state.
you can read more about controlled and uncontrolled components in the docs
I think you need to bind the input value and the state together. I am not sure how you're currently calling replaceCharacter but I would do something like this :
replaceCharacter(idx) {
return (
<input
key={idx}
value={this.state.input.replace(/#/g, " ")}
onChange={e => this.setState({input:e.target.value})}
/>
)
}
This way when you update your state with the onChange event the value of the state will be populated in the input.
I'm rendering to page a bunch of buttons based on what a user is typing. Basically the user starts typing and with each letter pressed a new set of buttons is rendered to the page where an obj.content contains the string being typed. This is all working fine, but I have one small problem. When a user first enters the program all of the buttons are rendered out to the page showing all options. I would like to show zero buttons if nothing is being searched for.
As of right now the normal state is looking for any matches of '', which every string that is searched contains so every button is rendered out to the screen.
Is there a way to render out zero buttons if my search fields are empty?
I have tried...
const RenderSearchResults = () => {
<div>
{renderResults}
</div>
}
const renderResults = this.props.data.filter(obj => {
return obj.content.includes(this.state.keyToFind);
}).map((obj, idx) => {
return (
<button name={obj.name} value={this.state.btnToFind} key={idx}>{obj.title} </button>
)
});
// FROM THE MAIN COMPONENT RETURN
return (
<input type="text name="keyToFind" placeholder="SEARCH" value={this.state.keyToFind} onChange={this.handleChange.bind(this} /> <br />
{this.state.keyToFind !== '' ? <RenderSearchResults /> : console.log("no input")}
)
// THIS WORKS BUT STARTS WITH ALL BUTTONS RENDERED OUT
return (
<input type="text name="keyToFind" placeholder="SEARCH" value={this.state.keyToFind} onChange={this.handleChange.bind(this} /> <br />
{renderResults}
)
However the above method doesn't update when a user starts typing. The only way I can get the search / render to work asynchronously is if I just place {renderResults} into the main component return without the if statement checking to see if the search field is empty. However, this results in all possible buttons being rendered out to page as the normal state.
Anyway to start with nothing being rendered out to the page?
I created a small example similar to what you are describing but much more simplified. Here I am checking if keyToFind is empty string and returning an empty array directly from the method that does the rendering.
class RenderButtons extends React.PureComponent {
state = {
keyToFind: ''
}
renderResults = () => {
if (this.state.keyToFind === '') return [];
const renderResults = ['a', 'b', 'c', 'aa']
.filter(obj => {
return obj.indexOf(this.state.keyToFind) >= 0;
})
.map((obj, idx) => {
return (<button name={obj} value={obj} key={idx}>{obj}</button>)
});
return renderResults;
}
handleChange = (event) => {
this.setState({ keyToFind: event.target.value });
}
render () {
const renderResults = this.renderResults();
return (
<div>
<input type="text" value={this.state.keyToFind} onChange={this.handleChange} />
{renderResults}
</div>
);
}
}
Here is a working example on codesandbox.