React input field value issue [duplicate] - javascript

This question already has answers here:
Why is setState in reactjs Async instead of Sync?
(8 answers)
The useState set method is not reflecting a change immediately
(15 answers)
Closed 9 months ago.
const [inputValue,setInputValue] = useState("")
handleChange = (e)=>{
setInputValue(e.target.value)
console.log(e.target.value,inputValue)
}
this is a simple input tag onChange function but the thing here inside the handleChange function when am logging the values e.target.value is what am typing on the field and inputValue which is the state am setting is actually empty so if i type let's say 'd' in the input field the value of the state is actualy empty and the next time i type something now it has the value 'd' which i have typed on previously...pls help me solve this

Yes. State change is async operation. console.log immediately executed. That mean console.log print the inputValue before the state update. That why if doing second its showing previous value.
Better you can detect the state change using useEffect
useEffect(()=>{
console.log(inputValue)
},[inputValue])

"the thing is that i want to do a search bar and because of this when i type let's say "a" or something search doesn't work because the state value is empty that time."
Setting state is an async process. When you try to log the state immediately after setting it all you're doing is logging the existing state before the component has re-rendered.
#prasanth is correct with their answer that adding a useEffect to watch for changes in the input state is the only way to log that new state.
If you're trying to write a search bar here's a quick contrived solution that filters out animal names from an array.
const { useState } = React;
function Example({ data }) {
// Initialise the input state
const [ input, setInput ] = useState('');
// When the input value changes
// update the state
function handleChange(e) {
setInput(e.target.value);
}
// `filter` the data using the input state,
// and then `map` over that array to return
// an array of JSX
function getFiltered(input) {
return data
.filter(el => el.toLowerCase().includes(input))
.map(el => <p>{el}</p>);
}
return (
<div>
<input
type="text"
placeholder="Find an animal"
onChange={handleChange}
value={input}
/>
<div>{getFiltered(input)}</div>
</div>
);
}
const data=["Alpine Goat","Beaver","Carolina Parakeet","Dragonfish","Eurasian Wolf","Frilled Lizard","Gila Monster","Honduran White Bat","Immortal Jellyfish","Japanese Macaque","Kooikerhondje","Leopard Gecko","Megalodon","Nova Scotia Duck Tolling Retriever","Orb Weaver","Pink Fairy Armadillo","Rhombic Egg-Eater Snake","Satanic leaf-tailed gecko","Tibetan Fox","Umbrellabird","Vervet Monkey","Whoodle","Xoloitzcuintli","Yeti Crab","Zonkey"];
ReactDOM.render(
<Example data={data} />,
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>

Related

Returning value from React child component

I am beginner in React so I apologize if this is a basic question. I am trying to build an online Sudoku grid using ReactJS but I am unable to render the digits in the field. I have made two components one being the Sudoku.js which renders the complete sudoku block and other being Node.js which consists of an input field wrapped inside a div. the functionality I want is to change the default value of the input field (which is "" in my case) whenever the user types a number. I have tried the below approach. It updates the value of my grid variable but does not show the updated value in the input field of Node. Kindly help.
Sudoku.js
const sudokugrid = (
<div>
{grid.map((row,rowIdx) => {
return (
<div key = {rowIdx} className="rowWrapper">
{row.map((col,colIdx) => {
var element = grid[rowIdx][colIdx].value;
const change = (event) => {
element = event.target.value;
grid[rowIdx][colIdx].value = element;
setGrid(grid);
return element;
}
return (
<Node
key = {colIdx}
onChangeValue = {change}
value = {element}
/>
)
})}
</div>
)
})}
</div>
);
here grid is a 2D array of 9x9 elements which have initial value of all the elements being "" , which are supposed to be updated when the user types in the value in the respective fields. the problem is that the value is updated when the user types the number but the number is not shown in the input field
The Node component is as follows:
function Node(props){
return (
<div className="box">
<input
className = "num"
type="number"
value = {props.value}
onChange = {props.onChangeValue}
/>
</div>
)
}
This is because react can't detect mutations. See this article. You have to store the sudoku grid in a state value and change the state with a callback function.
An example.
I suggest using an object map for storing the state because you can manage the values a lot easier.

React: useState array not updating

When you push an array, data is pushed. However if you check in console.log, data is not imported. It seems to be a delay. Can you tell me why is this happening?
is there solution for this?
Expected console.log result showing input, however empty array shows and if you click checkbox again then input appears.
const [checked, setChecked] = useState<number[]>([])
const handleAddListToArray = (id: number) => {
console.log(checked)
if (setChecked.includes(id)) {
setChecked(checked.filter((item) => item !== id))
} else {
setChecked([...checked, id])
}
}
--- checkbox compornent ---
const [isChecked, setIsChecked] = useState(false)
const handleChange = () => {
setIsChecked(!isChecked)
handleAddListToArray(id)
}
<Checkbox checked={isChecked} onClick={() => handleChange()} />
when you push an array, data is pushed however if you check in
console.log data is not inported. it seems to be a delay can you tell
me why is this happening?
The state-setting functions are asynchronous. In other words, if you wrote:
const [foo, setFoo] = useState(0);
setFoo(1);
console.log(foo); // logs 0, NOT 1
you would see 0 logged, not 1 ... at least initially. However, there'd be a log entry below that would show 1.
This is because set* function don't change the value in the function, but they do cause the component to be re-rendered after, which means the function is run again, and now uses the correct value..
however empty array shows and if you click checkbox again then input
appears.
It's because of this code:
setIsChecked(!isChecked)
Initially you set isChecked to an array:
setChecked(checked.filter((item) => item !== id))
But then you changed it to !isChecked ... a boolean. Once you change it to a boolean, you can't change it back to an array.
You check the setState-function if it includes the input, on your fourth row of code:
if (setChecked.includes(id))
I believe you want to chech the checked-state instead, like so:
if (checked.includes(id))
Also, consider using the useState-callback when you mutate your state based on the previous one. So instead of:
setChecked(checked.filter((item) => item !== id))
try:
setChecked((prevState) => prevState.filter((item) => item !== id))
You can also use this when you setting your isChecked state. This ensure that you get your latest known state and avoids getting in some wierd states because React updates the state asynchronous.
Some suggestions
if (setChecked.includes(id)) {
should be (setChecked is a function)
if (checked.includes(id)) {
For setChecked(checked.filter((item) => item !== id))
better usage will be
setChecked(prevCheckedValues => prevCheckedValues.filter((item) => item !== id));
This way you will always get the current checked values when you do a setChecked.
Another way to use this is via a useCallback and pass the dependency array as [checked]
If you want to print checked values each time its changed you can use a useEffect (docs) with correct dependency array.
Usage example
useEffect(()=>{
console.log("Checked", checked);
}, [checked])

React TS - Max character limit for textarea

So I have 2 issues. I've created a textarea component that will also keep track of how many characters the user has typed into the textarea.
Issue 1:
I'm trying to see if the current input length the user has typed is more than or equal to(>=) my maxLength prop if (value.length >= maxLength), but I get this error:
var maxLength: number | undefined
Object is possibly 'undefined'.ts(2532)
Could somebody explain why I'm getting this?
Issue 2:
Currently when you type a single character into the textarea, the character limit will remain at 0, and will only start to increment after the second character has been typed. Also if you type some text in the textatea and then delete it, it says the character limit is 1/X when it should be 0/X. How can I fix this?
Code:
import * as React from "react";
import "./styles.css";
interface ITextarea {
maxLength?: number;
}
const Textarea: React.FC<ITextarea> = ({ maxLength }) => {
const [value, setValue] = React.useState("");
const [charLimit, setCharLimit] = React.useState(`0 / ${maxLength}`);
const handleCharLimit = () => {
if (value.length >= maxLength) {
setCharLimit("You have reached the maximum number of characters!");
} else {
setCharLimit(`${value.length} / ${maxLength}`);
}
};
return (
<>
<textarea
value={value}
onChange={event => {
setValue(event.target.value);
handleCharLimit();
}}
maxLength={maxLength}
/>
{maxLength && <span>{charLimit}</span>}
</>
);
};
export default function App() {
return (
<div className="App">
<Textarea maxLength={50} />
</div>
);
}
Here's a CodeSandBox, forks are appretiated :)
Object is possibly 'undefined'.ts(2532)
ITextarea defines maxLength as an optional parameter. If you don't pass a value for it, its value will be undefined.
This happens because React state updates aren't immediate, they happen on the next render pass. You're calling handleCharLimit, but when you invoke it, value hasn't actually been updated yet, even though it's called after setValue. You should be using useEffect or useMemo to update your charLimit in response to a change in value.
See an updated version here: https://codesandbox.io/s/hungry-fermi-bsmny

Why is useState not triggering re-render?

I've initialized a state that is an array, and when I update it my component does not re-render. Here is a minimal proof-of-concept:
function App() {
const [numbers, setNumbers] = React.useState([0, 1, 2, 3]);
console.log("rendering...");
return (
<div className="App">
{numbers.map(number => (
<p>{number}</p>
))}
<input
type="text"
value={numbers[0].toString()}
onChange={newText => {
let old = numbers;
old[0] = 1;
setNumbers(old);
}}
/>
</div>
);
}
Based on this code, it seems that the input should contain the number 0 to start, and any time it is changed, the state should change too. After entering "02" in the input, the App component does not re-render. However, if I add a setTimeout in the onChange function which executes after 5 seconds, it shows that numbers has indeed been updated.
Any thoughts on why the component doesn't update?
Here is a CodeSandbox with the proof of concept.
You're calling setNumbers and passing it the array it already has. You've changed one of its values but it's still the same array, and I suspect React doesn't see any reason to re-render because state hasn't changed; the new array is the old array.
One easy way to avoid this is by spreading the array into a new array:
setNumbers([...old])
You need to copy numbers like so let old = [...numbers];
useState doesn't update the value only if it has changed so if it was 44 and it became 7 it will update. but how can it know if an array or object have changed. it's by reference so when you do let old = numbers you are just passing a reference and not creating a new one
Others have already given the technical solution. To anyone confused as to why this happens, is because setSomething() only re renders the component if and only if the previous and current state is different. Since arrays in javascript are reference types, if you edit an item of an array in js, it still doesn't change the reference to the original array. In js's eyes, these two arrays are the same, even though the original content inside those arrays are different. That's why setSomething() fails do detect the changes made to the old array.
Note that if you use class components and update the state using setState() then the component will always update regardless of whether the state has changed or not. So, you can change your functional component to a class component as a solution. Or follow the answers provided by others.
You can change state like this
const [state, setState] = ({})
setState({...state})
or if your state is Array you can change like this
const [state, setState] = ([])
setState([...state])
I was working on an array that had objects in it and I tried few of the above.
My useState is :
const [options, setOptions] = useState([
{ sno: "1", text: "" },
{ sno: "2", text: "" },
{ sno: "3", text: "" },
{ sno: "4", text: "" },
]);
Now I want to add more options with blank field on a click of a button I will use the following way to achieve my purpose:
<button
onClick={() => {
setOptions([...options, { sno: newArray.length + 1, text: "" }]);
}}
>
This solved my problem and I was able to re render the component and added an object to the array.
introduces an array of the component that is not the one of the hook. for instance:
const [numbers, setNumbers] = useState([0, 1, 2, 3]);
var numbersModify = []; //the value you want
and at the end:
setNumbers(numbersModify)
modify this numbersModify, when the hook refreshes it will return to 0 numbersModify and the hook will keep the state. Therefore, the problem of not seeing the changes will be eliminated.
:D
//define state using useState hook
const [numbers, setNumbers] = React.useState([0, 1, 2, 3]);
//copy existing numbers in temp
let tempNumbers = [...numbers];
// modify/add no
tempNumbers.push(4);
tempNumbers[0] = 10;
// set modified numbers
setNumbers(tempNumbers);
I dont have any proud of this but it works
anotherList = something
setSomething([])
setTimeout(()=>{ setSomething(anotherList)},0)
useState is a React hook which provides functionality of having State in a functional component.
Usually it informs React to re-render the component whenever there is change in useState variables.
{
let old = numbers;
old[0] = 1;
setNumbers(old);
}
In the above code since you are referring to the same variable it stores the reference not the value hence React doesn't know about the latest changes as the reference is same as previous.
To overcome use the below hack, which will not copy the reference instead it's a deep copy(copies the values)
{
let old = JSON.parse(JSON.stringify(numbers));
old[0] = 1;
setNumbers(old);
}
Happy coding :)

Dynamically created custom form components in react

See this gist for the complete picture.
Basically I will have this form:
When you click the plus, another row should appear with a drop down for day and a time field.
I can create the code to add inputs to the form, however I'm having trouble with the individual components (selectTimeInput is a row) actually updating their values.
The onChange in the MultipleDayTimeInput is receiving the correct data, it is just the display that isn't updating. I extremely new to react so I don't know what is causing the display to not update....
I think it is because the SelectTimeInput render function isn't being called because the passed in props aren't being updated, but I'm not sure of the correct way to achieve that.
Thinking about it, does the setState need to be called in the onChange of the MultipleDayTimeInput and the input that changed needs to be removed from the this.state.inputs and readded in order to force the render to fire... this seems a little clunky to me...
When you update the display value of the inputs in state, you need to use this.setState to change the state data and cause a re-render with the new data. Using input.key = value is not the correct way.
Using State Correctly
There are three things you should know about
setState().
Do Not Modify State Directly
For example, this will not re-render a
component:
// Wrong
this.state.comment = 'Hello';
Instead, use setState():
// Correct
this.setState({comment: 'Hello'});
The only place where you
can assign this.state is the constructor.
read more from Facebook directly here
I would actually suggest a little bit of a restructure of your code though. It's not really encouraged to have components as part of your state values. I would suggest having your different inputs as data objects in your this.state.inputs, and loop through the data and build each of the displays that way in your render method. Like this:
suppose you have one input in your this.state.inputs (and suppose your inputs is an object for key access):
inputs = {
1: {
selectedTime: 0:00,
selectedValue: 2
}
}
in your render, do something like this:
render() {
let inputs = Object.keys(this.state.inputs).map((key) => {
let input = this.state.inputs[key]
return (<SelectTimeInput
key={key}
name={'option_' + key}
placeholder={this.props.placeholder}
options={this.props.options}
onChange={this.onChange.bind(this, key)}
timeValue={input.selectedTime}
selectValue={input.selectedValue}
/>)
)}
return (
<div>
<button className="button" onClick={this.onAddClick}><i className="fa fa-plus" /></button>
{ inputs }
</div>
);
}
Notice how we're binding the key on the onChange, so that we know which input to update. now, in your onChange function, you just set the correct input's value with setState:
onChange(event, key) {
this.setState({
inputs: Immutable.fromJS(this.state.inputs).setIn([`${key}`, 'selectedTime'], event.target.value).toJS()
// or
inputs: Object.assign(this.state.inputs, Object.assign(this.state.inputs[key], { timeValue: event.target.value }))
})
}
this isn't tested, but basically this Immutable statement is going to make a copy of this.state.inputs and set the selectedTime value inside of the object that matches the key, to the event.target.value. State is updated now, a re-render is triggered, and when you loop through the inputs again in the render, you'll use the new time value as the timeValue to your component.
again, with the Object.assign edit, it isn't tested, but learn more [here]. 2 Basically this statement is merging a new timeValue value in with the this.state.inputs[key] object, and then merging that new object in with the entire this.state.inputs object.
does this make sense?
I modified the onChange in the MultipleDayTimeInput:
onChange(event) {
const comparisonKey = event.target.name.substring(event.target.name.length - 1);
const input = this.getInputState(comparisonKey);
input.selected = event.target.value;
input.display = this.renderTimeInput(input);
let spliceIndex = -1;
for (let i = 0; i < this.state.inputs.length; i++) {
const matches = inputFilter(comparisonKey)(this.state.inputs[i]);
if (matches) {
spliceIndex = i;
break;
}
}
if (spliceIndex < 0) {
throw 'error updating inputs';
}
this.setState({
inputs: [...this.state.inputs].splice(spliceIndex, 1, input)
});
}
The key points are:
// re render the input
input.display = this.renderTimeInput(input);
// set the state by copying the inputs and interchanging the old input with the new input....
this.setState({
inputs: [...this.state.inputs].splice(spliceIndex, 1, input)
});
Having thought about it though, input is an object reference to the input in the this.state.inputs so actually [...this.states.inputs] would have been enough??

Categories

Resources