Sorry about the confusing title, I really didn't know what the categorize this as. I was having an issue regarding changing a variable that was set to an Array element. I have since figured out a way to accomplish what I wanted, but this was still bugging me so I figured I'd ask here.
playCard(event){
let tempArray = this.state.playerDeck;
let playArray = this.state.playerDefaultCards;
playArray.push(tempArray[event.target.id]);
console.log(playArray[1].image); //logs image data
let tempObject = tempArray[event.target.id];
tempObject.image = "Test";
console.log(playArray[1].image); //logs "Test"
tempArray[event.target.id] = tempObject;
this.setState ({playerDeck: tempArray, playerDefaultCards: playArray});
console.log(this.state.playerDefaultCards[1].image); //Also logs "Test"
}
Basically I have 2 arrays of objects with image sources. When an image is clicked on, I wanted it to go from the playerDeck to the playerDefaultCards, and then I just wanted to change the image source in the playerDeck to a blank string.
I thought that if I made a temporary variable and set it equal to the array element, changed that temporary variables image property and then set the array element equal to that new object everything would work out.
However when I change tempObject.image = "Test" it changes in both the tempArray and the playArray, which to my knowledge shouldn't happen. This is the source of my confusion.
Obviously I'm missing something but so far I haven't been able to figure it out.
playArray.push(tempArray[event.target.id]) copies the reference at tempArray[event.target.id] to the object rather than the object itself.
You can use Object.assign to create a new object for the card you want to move, then you can edit the original.
You should also .slice() the state arrays to make sure you do not change them outside of setState.
Something like this:
playCard(event){
const tempArray = this.state.playerDeck.slice();
const playArray = this.state.playerDefaultCards.slice();
const selectedCard = Object.assign({}, tempArray[event.target.id]);
playArray.push(selectedCard);
tempArray[event.target.id].image = "Test";
this.setState({playerDeck: tempArray, playerDefaultCards: playArray});
}
One thing to watch out for is setState is fast, but it is not instant. A console.log called right after this.setState will possibly execute before the update is completed and return the old value. this.setState can take a callback function if you need to do something as soon as it finishes.
Object.assign is not supported by all browsers, so you may need a polyfill for it.
Object.assign documentation
Related
I am trying to implement some form validation to my project, of which change detection is a part, whereby if the user is "editing" and they try to navigate away, I want to show a prompt if they have unsaved changes. An issue I am facing is I have an Array that contains data from my database. I then wanted to create a second array, that is given the same data upon the page loading, so that if the user makes changes, the 2 arrays will no longer be the same and I can show the prompt.
However, I am having the problem that my second array is forever the same as the first. I am unsure how to fix this, I tried using an if statement such as:
this.service.memberData(this.memberId).subscribe(data => {
this.firstArray = data;
if (this.secondArray.length === 0) {
this.secondArray = data;
}
});
How can I make it so that secondArray initially takes the value of data, but never changes after this?
You need to clone the array, rather than just referencing the array itself.
Try this.secondArray = [...data];
I have created the following demo to help me describe my question: https://codesandbox.io/s/dazzling-https-6ztj2
I have a form where I submit information and store it in a database. On another page, I retrieve this data, and set the checked property for the checkbox accordingly. This part works, in the demo this is represented by the dataFromAPI variable.
Now, the problem is that when I'd like to update the checkboxes, I get all sorts of errors and I don't know how to solve this. The ultimate goal is that I modify the form (either uncheck a checked box or vice versa) and send it off to the database - essentially this is an UPDATE operation, but again that's not visible in the demo.
Any suggestions?
Also note that I have simplified the demo, in the real app I'm working on I have multiple form elements and multiple values in the state.
I recommend you to work with an array of all the id's or whatever you want it to be your list's keys and "map" on the array like here https://reactjs.org/docs/lists-and-keys.html.
It also helps you to control each checkbox element as an item.
Neither your add or delete will work as it is.
Array.push returns the length of the new array, not the new array.
Array.splice returns a new array of the deleted items. And it mutates the original which you shouldn't do. We'll use filter instead.
Change your state setter to this:
// Since we are using the updater form of setState now, we need to persist the event.
e.persist();
setQuestion(prev => ({
...prev,
[e.target.name]: prev.topics.includes(e.target.value)
// Return false to remove the part of the array we don't want anymore
? prev.topics.filter((value) => value != e.target.value)
// Create a new array instead of mutating state
: [...prev.topics, e.target.value]
}));
As regard your example in the codesandbox you can get the expected result using the following snippet
//the idea here is if it exists then remove it otherwise add it to the array.
const handleChange = e => {
let x = data.topics.includes(e.target.value) ? data.topics.filter(item => item !== e.target.value): [...data.topics, e.target.value]
setQuestion({topics:x})
};
So you can get the idea and implement it in your actual application.
I noticed the problem with your code was that you changed the nature of question stored in state which makes it difficult to get the attribute topics when next react re-renders Also you were directly mutating the state. its best to alway use functional array manipulating methods are free from side effects like map, filter and reduce where possible.
In the example here. I have to sliders and I drag them. Because I have two values I store them in useState as an array.
Then storing as an array the ComponentsWithArrayAsProp1 doesn't see changes in the state as it is the same array and does not re-render itself.
In the second example, I store values as values.toString(), but this is not a good solution.
What is a good solution for this case?
Then storing as an array the ComponentsWithArrayAsProp1 doesn't see changes in the state as it is the same array and does not re-render itself.
you are right we can solve it by creating new array everytime as follows,
function onChange1(values) {
setValues1([...values]);
}
but i think there is something wrong with react-slider .because your approach of setting array ,setValues1(values) works when we click on different points of slider1. but it deosn't work when we drag it. there is something else going wrong here ?
Option 1:
As you said, the ComponentsWithArrayAsProp1 doesn't see the changes because the array reference is the same, so it doesn't rerender. But you can save a copy of the array instead of the array itself.
function onChange1(values) {
setValues1(values.slice());
// Another way to do this
// setValues1([...values]);
}
Option 2:
You can also save the array numbers individually, like so:
const [values1Start, setValues1Start] = useState(0);
const [values1End, setValues1End] = useState(0);
function onChange1(values) {
setValues1Start(values[0]);
setValues1End(values[1]);
}
return (
<ComponentsWithArrayAsProp1 values={[values1Start, values1End]} />
);
I have a list of objects in React. I can .map over the objects. But I don't want them all. I need to filter based on a property. So I'm doing this:
const myFilteredList = myList.filter((item) => {
return item.name === 'the filter';
});
Once I do that, I cannot map over myFilteredList:
Uncaught (in promise) TypeError: myFilteredList.map is not a function
I understood this to mean that it was now an object rather than an array, so I tried this, aiming to convert it to an array:
let theArray = []
theArray = Object.keys(resultCampaigns).map(key => resultCampaigns[key]);
But I still can't map over theArray
What is the right way to do this?
The answer ended up being something unrelated to what I was trying to do, but I'll post it here so others can see. The things I was trying to do as laid out in my question will work. They didn't work for me because I was setting the value of myFilteredList in my render() by doing this:
const myFilteredList = this.state;
I believe that was overwriting my filtered array. Don't do that. I removed the above line of code, and now it's fine. I'm just accessing the array by doing
this.state.myFilteredList
rather than just myFilteredList
EDIT:
My real problem is outlined in a below comment. I had simply forgotten the curly braces in const { myFilteredList } = this.state; I have updated my code to reflect that tiny change, and it's fine now.
So I have a React component where I have:
getInitialState: function () {
return {
peopleDetails: []
}
}
peopleDetails will contain objects which will have property names like name, email, phone etc.
Here's the problem: I'll be passing methods to child components which should mutate the objects of this array. Some methods will just edit a person's email, another will edit the phone etc. Thus, they'll need to call setState.
As far as I understand, setState should return a new copy of the object. I find this impossible to do because things are difficult enough even if I have to mutate the object (I'll have to specify some filters to find it inside an array, then change its value etc).
I'm pretty sure a lot of React apps use data structures like these...where in the state you have an array holding objects and you'll need to do stuff to those objects. Is there some easy way to accomplish this, respecting the "rule" that each setState invocation should return a completely new state object?
If you really don't want to use immutable data, then you can directly mutate the properties you like and invoke a this.forceUpdate() instead of setState after mutations have occurred.
If you were to proceed with immutable data, then adding to that array would return a new array containing the new object. If you removed that person, you'd then return a new empty array. If you were to modify an item in-place in the array, then you'd build a new array using concat/slice to get all elements that haven't changed, then add your changed element, then add everything after the changed element's index.
For a more in-depth discussion on avoiding array mutations, check out this talk.