React - problem updating an array of objects using hooks - reactjs

I have tried a bunch of approaches but on this, but it's still bugging the hell out of me. I am using React 17.0.1 in this project.
I have a array of objects formatted like so:
gameNumberFields: [{ name: 'gameNum', placeholder: 'GGG', size: '3', value: '', dataindex: '0' }];
For now, there is just one object in the array, but there is always the possibility of more down the road (hence why it's an array).
In the code - this field is pre-populated on initialization - so the "value" of the first index in the array might be something like "123". I use initialState to make this happen:
const [gameNumberFields, setGameNumberFields] = useState(scratchTicketFields?.gameNumberFields ?? []);
When the display is shown to the user - this value is shown to the user in an field using the defaultValue.
return gameNumberFields.map((field, index) => {
const ref = React.createRef();
elemRefs.push(ref);
return (
<div className='d-inline-block ticket-number-inputs' key={`game-numbers--${index}`}>
<input
type='text'
id={field.name}
data-index={field.dataindex}
ref={ref}
className='theme-input'
placeholder={field.placeholder}
size={field.size}
maxLength={field.size}
defaultValue={field.value}
onBlur={(event) => handleGameNumberChange(event, field)}
onKeyUp={(event) => autoTab(event, elemRefs)}
required
/>
<span className='dash'>—</span>
</div>
);
});
}; // end GameIdFieldInputs
So far - so good. The problem I am having is in the onBlur event handler. For some reason - when the user changes the value to something else - it always goes back to the old value.
Here is the handler:
const handleGameNumberChange = async (event, field) => {
// get gameNum from the event target
const gameNum = event.target.value; // say this becomes 999
// do a deep copy of the gameNumberField state.
let gameIdField = JSON.parse(JSON.stringify(gameNumberFields));
// check that we are changing the right index in the array
const fieldIndex = gameIdField.findIndex((obj) => obj.name == field.name);
// make a new object changing the value to 999
let newGameObject = { ...gameIdField[fieldIndex], value: gameNum };
console.log('newGameObject', newGameObject);
//NOTE: At this point the newGameObject is correct and updated with the NEW gameNum of 999
// create a new array and PUSH the new game object onto it
let newGameIdArray = new Array();
newGameIdArray.push(newGameObject);
// Once pushed the array has the OLD game number in it . . . so 123 - WHY?!?!
console.log('newGameObjectArray', newGameIdArray);
setGameNumberFields(newGameIdArray); // updates with the 123 game number. . .
}; // end handleGameNumberChange
So in the method, I deep copy the gameNumberFields into a mutable object. I then update the object with the new gameNumber (from 123 to 999) and all works when I print it out with my console.log for newGameObject.
As soon as I push this object in the new Array - it changes back to 123! Can anyone see a flaw in my code here?
When I finally do call setGameNumberFields - it does set the state (I have a useEffect that prints out the values) but again, its always the OLD values.
Any help is welcome and appreciated!

Related

.filter() function creating loop in delete function - React

I've got a 'list' component, which allows you to add an item to a state array, and then display it from the state afterwards. These list items can then be removed by the user afterwards (or should be able to).
There's four state props in this component:
currentList: content in the input that's to be added to the list array
setCurrentList: what's used to change the content in the input
fullList: the full array list
setFullList: used to add the currentList content to the array, and removed
I'm using .filter() to create a copy of the state array, and then set the state afterwards in this function:
const deleteFromList = (e) => {
console.log("Delete button pressed")
console.log(e)
let fullList = props.fullListState
let setFullList = props.setFullListState
let filteredArray = fullList.filter(item => item)
setFullList(filteredArray)
}
However, every time I execute this function (i.e. when the delete button is pressed), it just creates a loop and the first two console.logs are just repeatedly done.
This is the full return function itself:
<>
<label className="setup-jobs-label">{props.label}</label>
<div className="setup-jobs-input-container">
<input className="setup-jobs-alt-input" type="text" onChange={onChange} value={props.currentListState} />
<button className="setup-jobs-add-button" onClick={addToList}>Add</button>
</div>
{ props.fullListState === [] ? null : props.fullListState.map(x => {
return <div className="setup-jobs-input-container" key={props.fullListState[x]}>
<p className="setup-jobs-input-paragraph">{x}</p>
<button className="setup-jobs-delete-button" onClick={deleteFromList(x)}>Delete</button>
</div>
}) }
</>
The important bit is the bottom conditional render, which checks to see if the state array is empty, and if so, not display anything. If it isn't, then it returns null.
Any advice would be appreciated - not sure what I'm doing wrong in the filter function.
In your onClick handler, you pass the result of the execution of deleteFromList, you should pass a reference to this function instead :
// note the '() =>'
<button className="setup-jobs-delete-button" onClick={() => deleteFromList(x)}>Delete</button>
See https://reactjs.org/docs/handling-events.html for more details about this.
Beside this, your filter logic does not seem right :
// this line only removes falsy values, but not the "e" values
let filteredArray = fullList.filter(item => item)
// you should implement something like this
let filteredArray = fullList.filter(item => [item is not "e"])
// this should work as we work on objects references
let filteredArray = fullList.filter(item => item !== e)

React useState not updating mapped content

I feel like im missing something that is staring me right in the face. I am trying to have content stored in an array of objects update when a checkbox is on or off. The console log is showing the object data is updating correctly so I assume my fault resides in not understanding useState fully?
const [statistics, setStatistics] = useState([
{
id: 1,
content: <div>Content1</div>,
state: true,
},
{
id: 2,
content: <div>Content2</div>,
state: true,
},
]);
In the component:
{statistics.map((item) => (item.state ? item.content : <></>))}
<input
type="checkbox"
onChange={(e) => {
let newArr = statistics;
e.target.checked
? (newArr[0].state = true)
: (newArr[0].state = false);
setStatistics(newArr);
console.log(statistics);
}}
/>
You are trying to change the state directly, instead you need to work with a copy of the state and make all changes to it.
Just replace in your code this string:
let newArr = statistics; // as link to base array
to
let newArr = [...statistics]; // as new copy of base array
and it will works.
React skips all state changes if they are made directly.
To create a new array as copy/clone of another array, in ES6, we can use the spread operator. You can not use = here, since it will only copy the reference to the original array and not create a new variable. Just read here for reference.
In your case, your newArray will refer to the old statistics and will not be detected as the new state. That is why no re-render takes place after you made changes to it.
So here, you can do this:
return (
<>
{statistics.map((item) => (item.state ? item.content : <></>))}
<input
type="checkbox"
onChange={(e) => {
setStatistics((prevStats) => {
const newStats = [...prevStats];
e.target.checked
? (newStats[0].state = true)
: (newStats[0].state = false);
return newStats;
});
}}
/>
</>
);

React.useState Hook only displaying length of object, not object itself

Basically, I have a component named Upload:
const Upload = () => {
const [scene , setScene] = React.useState([]);
// handleUpload = (event) => {
// console.log('Success!');
// }
function renderHandler(s){
console.log('[RENDER] calling...')
console.log(s);
if(scene.length == 0){
console.log('[RENDER] Denied!')
return(
<div>
Nothing is rendered..
</div>
)
} else {
console.log('[RENDER] Working...')
console.log('[RENDER] Received planes:');
console.log(blockState);
console.log(scene);
return (
<View top={blockState}/>
)
}
}
function parseNBT(input) {
setScene(scene.push('1'));
setScene(scene.push('2'));
console.log('scene:');
console.log(typeof scene);
console.log(scene);
console.log('\n+blockState:');
console.log(typeof blockState);
console.log(blockState)
}
return (
<Container>
Input NBT file <br/>
<input type="file" onChange={handleChange}></input>
{renderHandler(scene)}
</Container>
)
}
The issue here is, when I'm setting the scene's state in parseNBT, and console log scene, it gives me the array:
However, when I call it from renderHandler, it simply returns the length of the array, in this case it was 2
Very weird, maybe i'm missing something?
The .push returns the length of the array.
Return value
The new length property of the object upon which the method was called.
Try
setScene( currentScene => [...currentScene, '1'] );
setScene( currentScene => [...currentScene, '2'] );
To summerize briefly, you are treating 'scene' as a mutable object, when it is immutable. Meaning, when you are trying to do a 'scene.push' it is trying to modify an immutable object. A regular array is mutable, but not a react state array. Therefore, you do not want to give an update to scene directly, you want to take its previous state, concatenate it with your new desired value, then make that new value your new state.
Like so:
Replace your lines:
setScene(scene.push('1'));
setScene(scene.push('2'));
with:
setScene((scene) => [...scene, 1]);
setScene((scene) => [...scene, 2]);

React array state update

I am getting a book list from database and is stored in a state variable Book list also has book price field
const [books, setBooks]=useState([])
setBooks(data)
Each books object in the array has properties like BookName, Author , Price, Discount
I have rendered a html table like follows
return ( <div>
{books.map((x,i) => ( <tr>
<td>x.bookName</td>
<td>x.price</td>
<td><MyCustomTextInput onChange={e => handleChange(e, x.id)} value={x.discount}></MyCustomTextInput></td>
<tr></div>);
the sample code for MyCustomTextInput is as follows
function MyCustomTextInput(props)
{ return (<div><TextInput></TextInput> </div>)
} exports default MyCustomTextInput
The code where I update the price for corresponding object in "books" array is as follows
function handleChange(x,id){
var obj = books[id];
obj.price = obj.price - e.target.value; //I am correctly getting discount in e.target.value
}
Every thing works properly except the price after discount is not reflecting on the UI. though its getting updated properly in the array but not reflecting on the UI.
any help....
Experts -
This is setting a value:
function handleChange(x, id){
var obj = books[id];
obj.price = obj.price - e.target.value;
}
But it's not updating state. The main rule to follow here is to never mutate state directly. The result of bending that rule here is that this update never tells React to re-render the component. And any re-render triggered anywhere else is going to clobber the value you updated here since state was never updated.
You need to call setBooks to update the state. For example:
function handleChange(x, id){
setBooks(books.map(b =>
b.id === id ? { ...b, price: b.price - parseFloat(e.target.value) } : b
));
}
What's essentially happening here is that you take the existing array in state and use .map() to project it to a new array. In that projection you look for the record with the matching id and create the new version of that record, all other records are projected as-is. This new array (the result of .map()) is then set as the new updated state.
There are of course other ways to construct the new array. You could grab the array element and update it the way you already are and then combine it with a spread of the result of a .filter to build the new array. Any construction which makes sense to you would work fine. The main point is that you need to construct a new array and set that as the new state value.
This will trigger React to re-render the component and the newly updated state will be reflected in the render.
You need to setBooks to update state books.
function handleChange(x, id) {
setBooks(
books.map((item) =>
item.id === id ? { ...item, price: item.price - parseFloat(e.target.value) } : item,
),
);
}
To achieve that, you need to call setBooks after changing the price within handleChange method to re-render the component with the newly updated state.
It's simply like the following:
function handleChange(x,id){
var obj = books[id];
obj.price = obj.price - e.target.value; //I am correctly getting discount in e.target.value
setBooks([...books]);
}

react address lookup issue with changing final input value

I am building an address lookup functionality for an app, and my previous 2 questions have thus far, been unable to garner an answer. I am on the last step, and need to figure this out, so I turn to you stack overflow.
I start with an input, a button and a target input:
<input type='text' name='postcode-lookup' onChange={this.postcodeChange} />
<button onClick={this.searchPostcode}>Search</button>
<input type='text' name='target-for-data' />
Easy enough. Now for the functions attached to both of those elements:
postcodeChange = {e} => {
this.setState({'postcode': e.target.value});
}
searchPostcode = () => {
this.setState({'visible': true});
if(this.state.postcode.length . 0){
Axios.get('postcode look up api here')
.then(response => {
this.setState({'response': response.data});
})
}
}
Ok here, we have 3 state items: postcode, which we will set to an empty string '', visible, which is initially set to true, and response, which is an empty array, that we then populate with the response data of address objects.
My next step, was to display those addresses, so inside the render, I set a const that maps over the response array like so:
const result = this.state.response.map((item) => {
if(this.state.visible === true){
return(
<p key={item.id} onClick={addressClick}>{item.address1Field}</p>
)
}else {
}
})
Ok, so when we click the button, it will return a p tag filled with address data from the array. This also has a function, which is where my problem lies.
Inside of this function I set the visible state item to false, so that the addresses disappear. Easy enough.
But how do I then take the address that I clicked on, and populate it into the original input we started with?
I have tried many things, from setting the state in addressClick, targeting the innerHTML, e.target.value, e.target.innerHTML, and so on and so on for hours.
Any takers? Ideas?

Resources