I want to pass array from one component to the other in vueJs, which i am able to do with
<add-new-admin-modal
:permissions = "permissions"
</add-new-admin-modal>
In my other component which is a modal actually,
I am receiving the props as,
props: {
permissions: {
type: Array,
default: () => []
}
}
Here when i try to change the permissions array, it reflects the parent data, As mentioned in the documentation.
https://v2.vuejs.org/v2/guide/components-props.html#One-Way-Data-Flow
so i tried with spread operator
data () {
return {
statePermissions: [...this.permissions]
}
}
The statePermissions array is still empty when i try above method,
I even tried with Object.assign
data () {
return {
statePermissions: Object.assign([], this.permissions)
}
}
Still it doesn't work.
In my Modal, I am accessing it as
<div v-for = "permission in statePermissions" :key = "permission.id">
...someHtml here.
</div>
The main idea is, I have a component which gets the data through an api, then i have a modal which takes this data and updates it accordingly and submit it to an api.
when the modal is closed, the parent component should need to have the unedited data, so that if modal is reopened it should get unedited data.
In the process of using Modal, My parent component remains in the same state (It neither gets mounted nor changed), so their is no point in making the request for default data again from parent.
Your problem is probably that the default value for you prop is an empty array and you're assigning it to a local variable before the property is properly populated by the parent (or it might even be a lifecycle issue).
Try moving your assignment to the local variable to the mounted() hook or even better if you wan't it to be reactive watch your property:
watch: {
permissions(newValue) {
this.statePermissions = newValue
}
}
You also don't need to ...spread an array to assign it to an array.
Since permissions is an array of objects, when you make a copy of it, the resulting array is a shallow copy meaning it will contain the same references to objects. That's why modifying the new array's values update the old array's values as well. You need to create copies of the objects inside permissions.
If permissions only contains primitives, you can do something like this:
this.statePermissions = JSON.parse(JSON.stringify(this.permissions));
If permissions is of a certain type (i.e. new Permission()), you can map it (I think this is cleaner):
this.statePermissions = this.permissions.map(x => new Permission(x.propA, x.propB, etc.));
This way, each cloned object in statePermissions will have the same properties as the object it's copied from in permissions, but it's independent so modifications won't affect the parent it was created from.
There's a few other ways in this post.
Related
I am trying to update an object array but even though the correct values are passed to the function, the array remains blank. If I try add a second object, then somehow the first one gets added and I'm not sure what I'm doing wrong.
const [qualifications, setQualifications] = useState([{}]);
function handleAddQualification(values: any) {
console.log(values);
console.log(qualifications);
setQualifications((prev) => {
return [...prev, values];
});
console.log(qualifications);
}
The values that I'm passing get logged correctly and the 2 subsequent logs of qualifications both show an empty array of objects.
I simplified the object so in my screen shot I added 'one' and the value logs correctly, but the qualifications array remains blank. If I add a second entry of 'two' then for some reason it adds 'one' to the array.
Please share some insight as to what is going on here?
Here is example how event loop works :)
In your case:
Calling handleAddQualification
Log values, qualifications
Adding setQualifications to queue as async operation
Log qualifications again with the same result as from step 3
Here can works tasks from queue which was added before setQualifications
setQualifications updates qualifications
Take a look here for better understanding https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop
The state update calls are asynchronous and you can't see them by logging just after update call.
The first object is empty because you have defined it in your default state and you update your new state using the previous state because of this the empty object always stays as first element in the array. To fix this set your qualifications to
interface Qual {
subject?: string;
level?: string;
other?: string
}
const [qualifications, setQualifications] = useState<Qual[]>([]);
if you want to log state whenever it updates use an effect hook, something like this
useEffect(() => {
console.log({qualifications});
}, [qualifications])
I have the following method compares two array, one coming from the props and the other one from my own component. Every element that exists in my props array but doesnt exist in my components array is inserted in a third array with the added property called "destroy : true" so i can send it to the back end to be deleted from the database.
However for whatever reason my props is being updated instead of the variables i use in the method to do all this. i am not really sure why since i am not referencing the prop directly but i do copy its content to the variables in the method.
updateArray(){
let updatedArray = []
let oldArray = [...this.props.array]
oldArray.forEach(element => {
if(this.componentArray.indexOf(element) > -1){
updatedArray.push(element)
}else{
let newElement = element
newElement.destroy = true
updatedArray.push(newElement)
}
})
return updatedArray
},
why does this happen exactly? every other element in my component works fine except this.
Yes, you are copying the elements of the this.props.array array into a new array local to the method but given that the elements of the array are objects, both arrays are in the end containing same objects (references to the objects)
You can create shallow copy of the original element with the spread operator let newElement = { ...element } - this creates the completely new object and copy all properties of the original object. But be aware that if any property of original objects contains array/object, you have the same problem... just one level down
I am trying to pass an object (called a lecture) to the props from state using mapStateToProps. The object exists in an array of objects. I believe I am making a syntax error but am uncertain on exactly what is the correct syntax should be because the code below works for cycling through a parent object (that contains objects inside of it) and select the correct object inside of the parent.
//This code works to cycle through a parent Object that contains objects inside of it; returning the object inside that has the correct ID.
//It doesn't work in this situation ... where instead of having objects inside a parent object,
//there are obejcts inside a parent Array.
const mapStateToProps = (state) => {
const lectureID = state.selectedLectureID;
console.log(lectureID); <--- THIS SHOWS THE CORRECT ID OF THE OBJECT I AM TRYING TO READ.
const lectures = state.firestore.ordered.lectures;
console.log(lectures); <------ THIS SHOWS THE ARRAY OF OBJECTS, INCLUDING ONE OBJECT THAT HAS THE LECTURE ID FOUND ABOVE
const selectedLecture= lectures ? lectures[lectureID] : null; <--- ERROR HERE... TRYING TO CYCLE THROUGH THE ARRAY OF LECTURES AND PASS THE LECTURE THAT HAS THE CORRECT LECTURE ID. IF NONE HAVE THIS ID, THEN IT SHOULD RETURN "null".
console.log(selectedLecture); <--- THIS RETURNS "undefined", SUGGESTING THE CODE ABOVE DIDN'T READ THROUGH THE ARRAY OF OBJECTS AND SELECT THE ONE WITH THE CORRECT ID.
return {
selectedLecture: selectedLecture,
}
}
NOTE: The code above appears interpret the lectureID as an index in the array rather than an attempt to identify the objects ID within the array of Objects. How can I correct this?
I FOUND AN ALTERNATIVE SOLUTION:
Instead of trying to iterate across the array of objects inside mapStateToProps, I found it easier to simply pass the object with the specific ID as an object through Actions + Reducers, and interacting with the content via the state.
I wrote a custom form component called SingleFilterContainer
Within the parent component called FiltersContainer, I initialize a state filters which has an array of a single filter initially and respective setFilters function to modify the array of filters. And also has a button to add a filter. And in the render function I use filters.map to render SingleFilterContainer component multiple times. So far this works. But I want to add a delete filter button. I put this inside the SingleFilterContainer. When I click this it should update the state filters in the parent component and delete the ith filter and render the rest of the filters normally. Or render the whole map of filters again.
I'm new to react and hooks, and I feel like I'm missing something. I'm at it for the past three days and I'm a little bit lost.
Here is the sandbox https://codesandbox.io/s/editing-filters-j2qrp
I feel like how I'm handling state is completely wrong. And maybe I should use redux? But I want the SingleFilterContainer to be like an ephemeral form. Or should the delete filter button be within the parent component? and repeat it using map?
TL;DR Fixed fork
The problem in your code is that you call the function on render onClick={handleDeleteFilter(i)} the onClick expects a reference to a function, but if you want this code to work, then the method that you are passing from the parent to the child needs to return a function also.
Then your handleDeleteFilter will look like this:
function handleDeleteFilter(i) {
return function() {
filters.splice(i, i + 1);
setFilters(filters => filters);
}
}
Also in your case you don't need to pass i + 1 into splice as the second argument, as the second argument is the amount of items to remove. Which in your case is just 1. Docs
You pass the i to the first function and the second one will see it due to closure.
And then to the removing of an element.
Mutating the state is bad practice, so you can have a local variable with a copy of the state, which you can manipulate and then update the state. That way you can use whatever you want, but on the local variable.
So your updated handleDeleteFilter would look like
function handleDeleteFilter(i) {
return function() {
const clone = [...filters]
clone.splice(i, 1);
setFilters(clone);
}
}
or with .filter
function handleDeleteFilter(i) {
return function() {
const clone = filters.filter((item, index) => index !== i)
setFilters(clone);
}
}
this way you don't need a new variable as .filter returns a new array. Docs
Here's my fork.
The following is my changes.
function handleDeleteFilter(i) {
setFilters(filters => filters.filter((e, index) => index !== i));
}
Important notes:
Ideally, you need to assign an id property for each item in the array instead of comparing by index.
Don't directly mutate the state. .splice directly mutates the state. Learn to use .map, .filter & .reduce array functions
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.