How do I update/delete objects inside this.state array in React - reactjs

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.

Related

How to iterate through an array of objects and return one that has a specific object ID using JSX (in mapStateToProps of React-Redux)

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.

Store checkbox values as array in React

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.

Access elements of an Observable Array

I have a prop named blockers that returns an observable array of objects. These objects contain 'userId.'
How can I go about returning an array that contains these userIds?
Computed observables can help with situations like these. Make sure that your component is decorated as a mobx-react #observer, that way it will automatically make props observable.
#computed get userIds () {
return this.props.blockers.map(blocker => blocker.userId)
}

Passing Array as Props in vue Modal

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.

Accessing a property from an array of objects stored in state in React

I'm working with ReactJS on a project and using KnexJS to access a database. I would like to store and use the JSON objects I retrieve, in the state of a component as something along the lines of the following.
this.setState({tutors: tutors}) //Retrieval from database after promise
and then access their properties like so
this.state.tutors[0].email
this.state.tutors[0].firstname
However, when I try this, I get the following error
TypeError: Cannot read property 'email' of undefined
I have checked the console.log as well as using JSON.stringify to determine if the objects are retrieved by the time I need them. And it appears that they are. I simply cannot access their properties from state.
Is there something I can do about this? Or do I need to retrieve them from the DB one by one?
This could be happening, because at the moment your code tries to retrieve data from state, the data is not there yet -- though it's possibly added later. This is something that happens quite often, in my experience. You should probably check that an object exists, before trying to access a property on it -- and return null or undefined in case it doesn't exist:
this.state.tutors[0] ? this.state.tutors[0].email : null
EDIT (in response to the additional code samples):
Assuming your fetch function works ok and adds the fetched data to state (I would use forEach instead of map to push the elements into the array, or just map over the fetched array and add that to the state directly, without an intermediary array/variable/push -- but I think your code should work)...
The problem has to do with the render method, since when the component renders initially the data is not yet in state, and as such you're passing an empty array to the TutorTable component, which probably in turn produces the error you see.
The data gets added to state later, but at this stage the error on the initial render has already happened.
You could solve this by rendering the TutorTable conditionally, only when the data gets added to the state:
<div className="col-7">
<h3> My Tutors </h3>
{this.state.tutors.length > 0 && <TutorsTable tutors={this.state.tutors} />}
</div>
if you console.log out this.state.tutors in the render() method you should see in the console an empty array ([]) returned on the initial render, and then an array filled with data when the component re-renders when the data is added to the state.
I hope this helps.
It appears to me you are trying to get the first element of an array this.state.tutors[0].email
is the tutors really an array,could provide the format of the data that you console logged as you stated.This probably should be in the comment section but I have less than 50 rep so i cant comment.
you should check first tutors. if you are getting tutors an array then you can access its first element.
if(this.state.tutors[0]) {
console.log("first element of tutors array", this.state.tutors[0].email)
// anything you can acccess now of this element
}
and assign first tutors as an empty array in state.
state = {
tutors: []
}
in first lifecycle you have no data in this.state.tutors[0]
for this reason first check this.state.tutors[0] is ready or not like this
if(this.state.tutors[0])
{
var data=this.state.tutors[0];
data.push('your data');
this.setState({tutors:data})
}

Resources