Correct modification of state object arrays in ReactJS - arrays

I have an array of points located at this.state.points. I have an updatePoint() function I use to update individual points in this array. I thought that using .slice() would leave my state untouched until I call setState but when I console.log this.state.points after I set the value for points[i], I see it is already updated. How do I prevent my state from getting updated before I call setState?
updatePoint(i, point) {
console.log('updatePoint '+i)
let points = this.state.points.slice()
points[i] = point
this.setState({points: points})
}
This version also does not work:
updatePoint(i, point) {
console.log('updatePoint '+i)
let points = this.state.points.slice()
points.forEach((j) => {
points[j] = Object.assign({}, this.state.points[j])
})
points[i] = point
this.setState({points: points})
}

Array.slice does not perform a deep copy, so if your this.state.points is an array of objects, you'll need to copy those object items too.
You can copy an object using Object.assign.

You can use the spread operator (...) that came with ES6:
updatePoint(i, point) {
console.log('updatePoint '+i)
let points = [...this.state.points]
console.log("points", points);
points[i] = point;
console.log("state",this.state.points);
this.setState({points: points})
console.log(this.state.points);
}
You can test it here (and also try it checking the console.log on your own environment):
class Hello extends React.Component {
constructor(props){
super(props);
this.updatePoint = this.updatePoint.bind(this);
this.state = {
points: [{name: "uno"},
{name: "dos"},
{name: "tres"}]
};
console.log(this.state.points);
}
// i = 1, name: ocho
updatePoint = (i, point) => {
console.log('updatePoint '+ i)
console.log("spread", [...this.state.points]);
const newPoints = [...this.state.points];
console.log("points", newPoints);
newPoints[i] = point;
console.log("state",this.state.points);
this.setState({
points: newPoints
});
}
render() {
const nowState = this.state.points.map(item => <p key={item.name}>{item.name}</p>);
return (
<div>Hello
<button
onClick={() => this.updatePoint(1, {name: "ocho"})}
>Click
</button>
<p>Current state
{nowState}</p>
</div>);
}
}
ReactDOM.render(
<Hello name="World" />,
document.getElementById('container')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="container">
<!-- This element's contents will be replaced with your component. -->
</div>
More info on the spread operator:
From https://learn.microsoft.com/en-us/scripting/javascript/reference/spread-operator-decrement-dot-dot-dot-javascript :
Allows parts of an array literal to be initialized from an iterable expression (such as another array literal), or allows an expression to be expanded to multiple arguments (in function calls).
https://redux.js.org/docs/recipes/UsingObjectSpreadOperator.html
An alternative approach is to use the object spread syntax proposed for the next versions of JavaScript which lets you use the spread (...) operator to copy enumerable properties from one object to another in a more succinct way.

Related

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]);

Buggy movement of Object in Array with Redux and React

Basically I'm trying to move a single object within an Array of object, but when I move the same object once or twice it starts moving the other object in said array.
So I have tried making a new Array with .slice() then .shift(item) by it's index and then add it back in at the right index with .splice(newIndex, 0, item), once the array has been update I push it to the Redux store which updates my Megadraft(ie Draft.js) application.
I have also tried directly manipulating the original array, ie this.props.array (like your meant too with Redux) and using the keys inside of the objects instead of the indexes.
import React from 'react';
import { MegadraftPlugin, DraftJS, CommonBlock } from "megadraft"
export default class ImageGalleryBlock extends React.Component {
_moveImgOneBack = (e, images, index) =>{
e.preventDefault()
let newPlace = index - 1
if(newPlace == -1){
newPlace = images.length
}
const image = images.shift(index)
images.splice(newPlace, 0, image)
return this.props.container.updateData({ images: images })
}
_moveImgOneForward = (e, images, index) =>{
e.preventDefault()
let newPlace = index +1
if(newPlace > images.length){
newPlace = 0
}
const image = images.shift(index)
images.splice(newPlace, 0, image)
return this.props.container.updateData({ images: images })
}
render(){
return (
<CommonBlock {...this.props} actions={this.actions} title="Image
Gallery">
<BlockContent>
<div className='gallery-cms-block'>
{ this.props.images.map((obj, index)=> {
return(
<div key={obj.key} className="image-box">
<button title="Move image back one" className="move-button"
onClick={(e)=> this._moveImgOneBack(e,
this.props.data.images, index)}>◀ {index}</button>
<img className="image" src={`${obj.image.uri}?
id=${obj.image.id}`} />
<div>
<button key={obj.key} title="Move image forward one"
className="move-button" onClick={(e)=>
this._moveImgOneForward(e, this.props.data.images,
index)}>▶</button>
</div>
</div>
)
}) }
</div>
</BlockContent>
</CommonBlockMKII>
);
}
}
I expect the the button(ether forward or backward) to move said item and only the said item.
The results are that it will move the item once... maybe twice then get suck moving all the other items in the array.
... your using shift wrong:
array = ['foo', 'bar', 'not', 'feeling', 'welcome', 'by jack', 'ass users']
array.shift(whatEverIndex)
the output will always be the first index, i.e 'foo', and
because your index is correct and your using
array.splice(newIndex, 0, item)
properly your array is changing in a strange fashion.
Try copying the desired item then delete it with .splice(), like this:
const item = array[index] //copy item
array.splice(index, 1) //remove old item
array.splice(newIndex, 0, item) //place item
funny that none of you guys NaN, laruiss, Antoine Grandchamp, J-Alex took the time to actually do what you should on stackoverflow... you know help someone out. damn vete a cascarla, Good luck Reece hope this solved it for you.
Thanks #Whitepaw,
I've updated my code with:
_moveOneImgBack = (newArray, index) =>{
const arrayLength = newArray.length - 1
const newBackPlace = index == 0 ? arrayLength : index - 1
const image = newArray[index]
newArray.splice(index, 1)
// const image = images.shift(index)
newArray.splice(newBackPlace, 0, image)
this.props.container.updateData({ images: newArray })
}
and it now works perfectly, I got stuck on the fact it might have something to do with redux immutables. So thats for pointing out the misuse of .shift()

React how to convert string to object property

In a React project, I want to dynamically access an object property name using a string variable I pass through props. I am able to pass a value as string; however, when I try to use it to access the object property it doesn't work.
Here's an example:
const passedField = 'title'; // this comes from props
const book = { title: 'Sample title', content : 'some content' };
book.passedField; // returns undefined
I access title like this book.title
I want to access it like book.passedField but I get undefined on console.
How can I achieve this?
book.passedField will be returning undefined because there is no passedField in book. Instead use bracket notation as below
const book = { title: 'abc', pages: 350 };
const passedField = 'pages';
console.log(book[passedField])
What you're using is called a dot notation property accessor. What you need to use is a bracket notation property accessor.
book.title and book['title'] are the same, which means you can assign title to a variable, and use the variable name instead like this.
const passedField = 'title';
book[passedField]; // same as book['title']
You might only be familiar with bracket notation with Arrays, like myArray[0], but they work with Objects too! (because Arrays in JavaScript are actually objects)
Solution
const books = [
{
title: 'The Art of War',
contents: 'An ancient Chinese military treatise dating from the Spring and Autumn Period'
},
{
title: 'Winnie the Pooh',
contents: 'Pooh is naive and slow-witted, but he is also friendly, thoughtful, and steadfast.'
}
]
class Book extends React.Component {
constructor(props) {
super(props);
this.findBookByFilter = this.findBookByFilter.bind(this);
}
findBookByFilter() {
let result = null
if (books) {
const {passedFilterField, filterText} = this.props;
result = books.filter(book => {
return book[passedFilterField].toLowerCase().includes(filterText.toLowerCase());
}).map(book => <p>{book.title}</p>)
}
return result;
}
render () {
return this.findBookByFilter();
}
}
class App extends React.Component {
render() {
return (
<Book
passedFilterField='contents'
filterText='thoughtful'
/>
)
}
}
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Notes
I moved your {book && book.filter} logic to a function called findBookByFilter
I used includes instead of indexOf in the filter
I used destructuring assignment for this.props
I return the matched title, rather than <Link /> for demo purposes.
Documentation
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Property_accessors
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/includes
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment
You can access object properties using book[passedField]

Append array of values to the current array in a state in React JS

I have the state values as
this.state = {
emp: [
{id: "1", name: "A"}
{id: "2", name: "B"}
{id: "3", name: "B"}
]
}
How can I add an array like var arr = {id:"4", name:"D"} to the state emp without removing the current values of array. I just want to append the new array of values to the current state array. Can anyone help?
In modern JavaScript you can use the spread operator:
Add a single item
addItem = item => {
this.setState({
emp: [
...this.state.emp,
item
]
})
}
Add multiple items:
addItems = items => {
this.setState({
emp: [
...this.state.emp,
...items
]
})
}
The spread operator places all the elements in this.state.emp in a new array instance and item gets appended as the last element.
You should not mutate a component's state with other means than setState as your rendered data will get out of sync.
just use concat
this.setState({ emp: this.state.emp.concat('new value') })
The reasons why concat is better than push, unshift are
Array.push
Array.prototype.push allows us to push elements to the end of an
array. This method does not return a new copy, rather mutates the
original array by adding a new element and returns the new length
property of the object upon which the method was called.
Array.unshift
To add elements to the very beginning of an array. Just as push, unshift does not return a new copy of the modified array, rather the new length of the array
Both the ways changes the mutation state of an array. A mutation term is meant to be unchanged because it is our original source.
array.concat
The concat() method is used to merge two or more arrays. This method does not change the existing arrays, but instead returns a new array.
However, You Object.assign() too, that creates a deep copy of object assigned to it.
let emp = Object.assign([],this.state.emp); //type of an array
result
You need to update if using functional setState(since you are updating state based on prevState) and spread syntax like
this.setState(prevState => ({
emp: [
...prevState.emp,
{id:"4",name:"c"}
]
}))
If using a functional component, voila a simple example:
const [hiddenDivs, setHiddenDivs] = useState([1, 2, 3, 4]);
const handleCatDivTitleClick = (divNum: number) => {
if (hiddenDivs.includes(divNum)) {
setHiddenDivs(hiddenDivs.filter((d) => d !== divNum)); //REMOVE FROM ARRAY
} else {
setHiddenDivs([...hiddenDivs, divNum]); //ADD TO ARRAY
}
};
<div class={`${style.catDiv} ${hiddenDivs.includes(1) ? style.catDivHide : ''}`}>
<div class={style.catTitle} onClick={() => handleCatDivTitleClick(1)}>
Imagine a list of categories like this. All begin "hidden" (shrunk-up).
</div>
</div>
<div class={`${style.catDiv} ${hiddenDivs.includes(2) ? style.catDivHide : ''}`}>
<div class={style.catTitle} onClick={() => handleCatDivTitleClick(2)}>
You want to shrink/expand category based on clicking title.
</div>
</div>
<div class={`${style.catDiv} ${hiddenDivs.includes(3) ? style.catDivHide : ''}`}>
<div class={style.catTitle} onClick={() => handleCatDivTitleClick(3)}>
Basically, a home-rolled accordian-type display.
</div>
</div>

Reactjs, how to instanciate object from array, and update render

I'm struggling with reactjs for no reason. I'm a little confused about the magic behind and I'm not able to perform a simple operation of adding object / removing object from an array and display it.
I my parent, I have a method which on click append a new element:
appendNewPma(){
var newPma = this.state.pma.slice();
newPma.push(PmaType1);
this.setState({pma:newPma})
}
then my render method is like that:
render() {
return (
<div>
<a className="waves-effect waves-light btn" onClick={this.appendNewPma}>new</a>
{this.state.pma.map((Item, index) => (
<Item
key = {index}
ref = {"pma" + index.toString()}
onDelete = {() => this.onDelete(index)}
title = {index}/>
))}
</div>
);
}
Append work fine, but my array doesn't contain an object to display but rather a magical function that I don't understand.
But when I try to delete an object:
onDelete(idx){
console.log(idx);
var pma = this.state.pma.slice();
pma.splice(idx, 1);
this.setState({pma:pma})
}
When I delete from the array, no matter what index I will remove, it will only remove the last object. I know my code is not ok, but I have no idea how you can render element for an array of object (here my array is list of function constructor).
It will work better if I could get a straight ref to my object. Of course, I tryed to removed from the ReactDom, but was complening I was not updating from the parent...
I just want a simple array push/pop pattern with update.
Thanks for your help
Try below code. hope so it solve your issue.
addToArray = (event) => {
this.state.pma.push({"name ": "xyz"});
this.setState(
this.state
)
}
removeFromArray =(index) => {
var updatedArr = this.state.pma.splice(index, 1);
this.setState({arr : updatedArr})
}

Resources