Splice method in React - javascript

I'm trying to use splice to add new components into an array. If I use concat all the elements are added properly at the end, but what I also need is add at the beginning or in the middle of the array using splice. Any suggest ?
class App extends React.Component {
state = {
components: []
};
addNewElement = (element) => {
this.setState(prevState => ({
//Works fine
//components: prevState.components.concat(element)
components: prevState.components.splice(0, 0, element)
}));
};
}

splice() returns an array of elements that have been removed from the array. If no elements were removed, splice will return an empty array.
However, splice will change the contents of the array it's called on. You need to set the state on the updated array, not on what splice returns.
Try this method:
addNewElement(element) {
this.state.components.splice(0, 0, element);
this.setState({ components: this.state.components });
}
Below is a working snippet to demonstrate how you can insert a new element at a selected index using splice within a React component.
CodePen Demo

Be careful to note the difference between methods that mutate the array on which they are called and methods which returns mutated versions of the array on which they are called.
prevState.components.splice(0, 0, element) returns a new array containing the elements which have been removed, which for your purposes is going to be nothing.
Notably, splice also mutates the components array; mutating your State elements is A Bad Thing To Do; one simple way to avoid that is to create a clone of your array and splice that.
this.setState(prevState => {
const components = prevState.components.slice(0);
components.splice(0, 0, element);
return { components };
});
This is functional, but relatively inelegant.
Other options you could consider would be to use React's immutability helper or use slice to divide your original array in two then concat all the bits together:
const i = // index at which to insert the new elements
const left = prevState.components.slice(0, i)
const right = prevState.components.slice(i)
return {
components: left.concat(elements, right)
}

Array#splice works in situ and mutates the original array. You have to make a new instance (copy it) with Array#slice and then modify it.
addNewElement = (element) => {
const newArr = prevState.components.slice();
newArr.splice(2, 0, 'foo'); // add 'foo` string at `2nd` index
this.setState(prevState => ({
components: newArr;
}));
};

Related

Function just re render one time

I got a problem with this Function. When I trigger this function it only re render the component the first trigger. After that not any more. I cant find the problem :(
function selectAnswer(id, questId) {
let newArr = questions
for(let i = 0; i < newArr.length; i++){
if(questId === newArr[i].id){
const changedAnswers = newArr[i].answers.map(answer => {
return answer.id === id ?
{...answer, selected: !answer.selected} :
{...answer, selected: false}
})
newArr.forEach(element => {
if(questId === element.id){
element.answers = changedAnswers
}
})
}
}
setQuestions(newArr)
}
You're never actually updating the state. This doesn't create a copy of the array, it just duplicates a reference to the array:
let newArr = questions
So this isn't setting state to a new array, but just a reference to the same array:
setQuestions(newArr)
Additionally, instead of creating new state, you are mutating the existing state:
element.answers = changedAnswers
Start by creating a new array, even if it contains the same elements as the original:
let newArr = [...questions];
Then, when you want to modify one of those elements, instead of modifying the existing element you would instead replace it with a new one. So instead of this structure:
newArr.forEach(element => {
});
You could instead replace your new array with a .map() over itself:
newArr = newArr.map(element => {
});
And within that .map() you would return either the unmodified object or the replacement object:
newArr = newArr.map(element => {
if(questId === element.id) {
return {...element, answers: changedAnswers};
} else {
return element;
}
});
Overall, the idea here is that you don't want to loop over an existing array, modify its values, and set that array back to state. That's not "updating state" in the React sense but instead it's "mutating state". Instead, create a new array and populate it with the objects you want. Those objects can be a mix of unchanged objects from the existing array and replaced (not modified) objects from the existing array.

Why doesn't the spread operator add properties to my array?

I'm working with a React useState variable. I have an array of objects that has 18 objects at this top level. I'm trying to update the object at the 14th index and then return the remaining objects after it. At first I was directly mutating the state by pushing my changes to it like so:
setFormWizard(wizard => {
wizard.controls[14].trash = true;
return ({
...wizard,
wizard: wizard.controls[14].group.push(...newSection.slice(0, 5)),
});
});
This works, but I'm told this isn't good practice because React may not catch the update if you push directly to the state in certain cases. So now I'm trying to use the spread operator instead.
What's happening is the first 14 objects are returning. The object I'm modifying is returning, but the rest of the objects that come after the 14th index are not returning. What am I doing wrong?
setFormWizard(wizard => {
const subjectControls = wizard.controls[14]
const groups = [...controls.group, ...subjectAddressCopy.slice(0, 5)]
return ({
...wizard,
controls: [
...wizard.controls.splice(0,14), // keep first 14 entries
{
...subjectControls,
group: groups,
trash: true
} // modify the 15th entry
...wizard.controls.splice(15) // this doesn't return the remaining controls
],
});
});
bcz splice changes in actual array you need to use slice
const arr = [1, 2, 3, 4]
arr.splice(0,2)
console.log('splice change in array so actual value of arr is :', arr)
const arr1 = [1,2,3,4,5]
// it will take slice(start, end) but end not included in return value
const cutSection = arr1.slice(1, 3);
console.log('portion of array', cutSection)
You might've wanted to use slice to return everything upwards from index 15.

Prevent original array from getting changed

I have an array that consists of some geoJSON data. I want to create a new array consisting of the data with one more key in the properties section. But it keep changing the original array. How can I prevent it from changing the orignial array?
const [citys, setCitys] = useState(someGeoJson)
const [manipulatedArray, setManipulatedArray] = useState([])
function createManiArray() {
let currentManipulatedArray = []
citys.features.forEach((city) => {
let currentCity = city
currentCity.properties = { ...city.properties,
value: Math.random()
}
currentManipulatedArray .push(currentCity)
//My problem now is that this changes the orginal city array, how can i prevent that from happening?
})
setManipulatedArray(currentManipulatedArray)
}
I think many of this kind of problems arise in the moment you use a forEach to essentially map values to another list.
The "map" method on an array does exactly that
const manipulated = citys.map(city => ({
...city.properties,
value: Math.random
}));
This way you don't have to worry about references / modifying your original array.
P.S. it is also worth noting that storing a variable with useState that's essentially derived from another state variable is not an ideal thing to do.
You might want to reconsider how your state is managed to essentially have a single source of truth (being your "citys") variable :)
You're not using setManipulatedArray to set the state.
You should be using map to create a new array of objects instead of mutating the original array.
const [cities, setCities] = useState(someGeoJson);
const [manipulatedArray, setManipulatedArray] = useState([]);
function createManiArray() {
// Create a new array of objects with an updated
// properties object
const updated = cities.features.map(city => {
return {
...city,
properties: {
...city.properties,
value: Math.random()
}
};
});
// Set the new state with that array
setManipulatedArray(updated);
}

Cannot Array.filter properly

Despite of numerous thread on the subject I didn't manage to remove an item of a string based Array using Array.filter method. Here is the filter method in a context of a mutation of a Vuex store.
UPDATE_FILTERS_GEOLOGIES: (state, payload) => {
if (state.filters.geologies.some(elem => elem === payload)) {
state.filters.geologies.filter(elem => !elem.includes(payload))
} else {
state.filters.geologies.push(payload);
}
}
The filter method is call but the item is not removed. I ended up using Array.splice method:
let position = state.filters.geologies.indexOf(payload);
state.filters.geologies.splice(position, 1)
Could you tell me what I'm doing wrong?
based on MDN:
The filter() method creates a new array with all elements that pass the test implemented by the provided function.
so basically what is wrong in your code is that:
state.filters.geologies.filter(elem => !elem.includes(payload))
is not being saved in any variable, thus the filtered array isn't being used. in order to make it work you need to assign the return value of the filter. something like:
state.filters.geologies = state.filters.geologies.filter(elem => !elem.includes(payload))
or as vladislav said:
state.filters.geologies = [...state.filters.geologies.filter(elem => !elem.includes(payload))]
Until splice() method is mutating original array, filter(), map(), or some() methods returns new array and leave the original array intact. So, you can use filter() method, but then you should replace original array with returned array. For example:
UPDATE_FILTERS_GEOLOGIES: (state, payload) => {
let g = state.filter.geologies
let p = payload
g.some(e => e === p)
? (g = { ...g.filter(e => !e.includes(p)) })
: g.push(p)
}

Angular 4 Operator to add element in beginning of array and return array

Add the element to beginning of the array and returns the array as well. this is what i am looking for in typescript.I am using angular with redux and looking to write the reducer function which demands this solution. I cannot use unshift as it does not returns the arry and even tries splice(0,0,newObject) - doesn't work. Any other idea, help. concat does the work but adds to last of the array.
function Addrow(state:State,action:any):State{
return Object.assign({}, state,{
displayCodes: { list: copyobject.list.concat(state.displayCodes.list.length)},
filteredCodes: { list:copyobject.list.concat(state.displayCodes.list.length)}
});
There is no method that will add an element to the beginning of the array and return the array.
Using splice should work, I'd recommend trying it again:
function Addrow(state:State,action:any): State {
const length = state.displayCodes.list.length;
const list = copyobject.list.splice(0, 0, length);
return Object.assign({}, state, {
displayCodes: { list },
filteredCodes: { list }
});
}
You can also use unshift, just not as a one-liner.
function Addrow(state:State,action:any): State {
const length = state.displayCodes.list.length;
const list = copyobject.list;
list.unshift(length);
return Object.assign({}, state, {
displayCodes: { list },
filteredCodes: { list }
});
}

Categories