Re-initializing react state array of objects leads to undefined values - javascript

I have two ReactJS components, a page called Projects with a Table component. When I initialize the state that has Table's data like so, the Table component renders fine:
const [tableData, setTableData] = useState([{
client: "anna",
name: "no",
email: "no",
date: new Date(),
progress: 0
}])
where the Table component just runs:
<tbody>
{props.data.map((obj,index) =>
<tr onClick = {(event) => {props.updateIndex(index)}}>
<td>{obj.client}</td>
<td>{obj.name}</td>
<td>{obj.email}</td>
<td>{obj.date}</td>
<td>{obj.progress}</td>
</tr>
)}
</tbody>
However, once I attempt to overwrite this state with database data, like this, although the array of objects prints out correctly, the table cannot read the data:
const userInfo = async () => {
await Promise.all(res.data.map(async (el, index) => {
const contents = await axios.get('/api/user/'+ el.userIDS[0] + '/info');
let clientName = contents.data.firstname + ' ' + contents.data.lastname;
let email = contents.data.email;
let info = {
client: clientName,
name: el.Name,
email: email,
date: date,
progress: el.progress
};
temp.push(info)
}));
}
setTableData(temp)
userInfo();
Both times, the state array prints out nicely. This screenshot shows the state (tableData) both at initialization and after the userInfo() has been run, and the format seems to be the same:
I guess I'm just confused as to what the mistake is that I am making, since in the console both object arrays look alike, but are still off from one another. I have tried changing the structure from a 2D array to an array of objects, which I like better, but both still cause issues.

You should call setTableData(temp) inside the userInfo function:
const userInfo = async () => {
const data = await Promise.all(
res.data.map((el, index) => {
return axios.get("/api/user/" + el.userIDS[0] + "/info");
})
);
const temp = data.map(contents => {
let clientName = contents.data.firstname + " " + contents.data.lastname;
let email = contents.data.email;
let info = {
client: clientName,
name: el.Name,
email: email,
date: date,
progress: el.progress
};
return info;
});
setTableData(temp);
};
Also in my suggestion, all you async call should go inside useEffect react function

Related

Filtering data after fetching in React

I need to make a list of objects based on combined data from 2 arrays, one comes from a localStorage and the second one from Django backend. First of all objects from localStorage are displayed by showCart() function
export const showCart = () => {
if (typeof window !== undefined) {
if (localStorage.getItem("cart")) {
return JSON.parse(localStorage.getItem("cart"));
};
};
};
it returns data in this format: FE: { id: 1, amount: 7, size: "L", product: 1 }. product is the Foreign Key needed to match data from other array.
The second array comes form a backend and it is feched by getAllProducts() function
export const getAllProducts = () => {
return fetch(`${url}/products/`, {method: "GET"})
.then((response) => {
return response.json();
})
.catch((error) => console.log(error))
};
It returns data in this format: FE { name: "Red", id: 3, price: 33, image:"some-url"}
​​
Now I need to create another list of objects by merging then by product of an object in first array with id of an object from the second one. The objects in the third array need to contain amount and size from first array as well as name, price and image from the second one. In the end I want to store it in useState().
This is what I came up with, I guess my code stops working arter first for loop:
const [cart, setCart] = useState([]);
const CheckAnonymousCart = () => {
getAllProducts()
.then((data) => {
const localCart = showCart();
var products = [];
for (let i = 0; i < localCart.lenght; i++) {
for (let y = 0; y < data.lenght; y++) {
if (localCart[i].product === data[y].id) {
console.log(localCart[i].product, data[y].id)
const item = {
name: data[y].name,
price: data[y].price,
image: data[y].image,
amount: localCart[i].amount,
size: localCart[i].size,
}
products.push(item)
break;
}
}
}
setCart(products);
})
.catch((error) => console.log(error))
};
​​Any thoughts?
In addition to Jacob's comment, you probably want to avoid FETCH'ing all products from the DB, because it requires more DB resources, most of the info is not required, and it makes the for-loop take longer to JOIN both lists.
Ideally, you would use a parameterized query like so:
return fetch(`${url}/products/?id=1&id=2&id=3`, {method: "GET"})
Where ?id=1&id=2&id=3 are a subset of the product IDs that you're retrieving.
Note: You will also want to sanitize/validate the product IDs in localStorage, because the data can be modified by the end-user, which is a potential attack vector by malicious users.
The problem could simply be the typo from the for loop conditions, but you can also accomplish this more succinctly using the JS ES6 methods:
const products = localCart.map(item => {
const match = data.find(x => x.id === item.product);
return {
amount,
size,
name: match?.name,
price: match?.price,
image: match?.image
}
});

React what is the correct shape of data in setState?

Hello i am updating non trivial data in react state and i am not sure what should data looks like. I am updating timetable item. It looks like this:
I am updating everyTime subject only in some day and slot.
my function looks like this now
updateColumn(newSubject,dayId,order){
console.log("tu som");
console.log(newSubject);
console.log(dayId);
console.log(order);
console.log(this.state.schedule);
//Problematic part:
let newSchedule = this.state.schedule;
newSchedule.days[dayId].slots[order] = newSubject;
console.log("newSchedule");
console.log(newSchedule);
this.setState({schedule:this.state.schedule});
console.log("this new state");
console.log(this.state.schedule);
//State changed but error shows
}
I tried this but this is not correct becouse slots and days are arrays of objects.
this.setState({...this.state, schedule: {
...this.state.schedule,
days: {
...this.state.schedule.days,
[dayId]: dayId,
slots: {
...this.state.schedule.days.slots,
[order] : newSubject
}
})
How it should be right ways please?
This might be what you need:
this.setState((prevState) => {
const updatedSchedule = { ...prevState.schedule };
const updatedDays = [...updatedSchedule.days];
const updatedDay = {...updatedDays[dayId]};
const updatedSlots = [...updatedDay.slots];
updatedSlots[order].subject = newSubject;
updatedDay.slots = updatedSlots;
updatedDays[dayId] = updatedDay;
updatedSchedule.days = updatedDays;
return {
schedule: updatedSchedule
};
})
assuming dayId and order are indexes.

Pushing responses of axios request into array

I have been pounding my head against this problem, and need help with a solution. I have an array of IDs within a JSON, and I am iterating over that array and making a GET request of each ID. I want to then push the response of those GET requests into an array.
Here is the function I am using to push the registrations into the array. It is iterating through an array of IDs:
getRegistrations = async (event) => {
let registrations = [];
await event.registrations.forEach(registration => axios.get('/event/getRegistration', {
params: {
id: registration
}
}).then(res => {
registrations.push(res.data.properties)
}
).catch(err => console.log(err)));
return registrations;
};
Here is where I am calling that code:
render() {
let event = this.getEvent();
let registrations2 = [{
age: 19,
bio: 'test',
firstName: 'hello',
lastName: 'bye',
password: 'adadas',
telephone: "4920210213"
}];
if (this.props.listOfEvents.events.length !== 0 && !this.props.listOfEvents.gettingList && event) { //check if the array is empty and list has not been rendered yet
let columns = [];
let registrations = this.getRegistrations(event);
console.log(registrations);
let eventProperties = event.properties[0];
Object.keys(eventProperties).forEach(key => columns.push({
title: eventProperties[key].title,
dataIndex: key,
key: key
}));
console.log(registrations);
console.log(registrations2);
return (
<h1>hi</h1>
)
}
return <Loading/>
}
When I console-log 'registrations' vs 'registrations2' they should be very identical. However, in the javascript console on Google Chrome, 'registrations appears as '[]' where 'registrations2' appears as '[{...}]'.
I know that it is an issue related to promises (I am returning the registrations array before actually pushing) but I have no idea how to fix it! Some friendly help would be very much appreciated!
I recommend Promise.all, it will resolve single Promise after all promises have resolved. And technically async function is also promise so it will return promise.
here the example.
https://codesandbox.io/s/jzz1ko5l73?fontsize=14
You need to use componentDidMount()lifecycle method for proper execution and state to store the data.
constructor (props) {
super(props);
this.state = {registrations :[]}
}
componentDidMount () {
let response = this.getRegistrations()
this.setState({registrations : response});
}
Then access that state in render method. It's not good practice to call api from render mothod.
Since getRegistrations(event) returns a promise, you should perform operations on its return value inside then.
Instead of
let registrations = this.getRegistrations(event);
console.log(registrations);
Do this
this.getRegistrations(event).then(registrations => {
console.log(registrations);
// other operations on registrations
});

React/JavaScript: Adding property to nested array in state in React

I need to add an id number to the nested array in data called action. The code I'm using is:
const { data } = this.state
const newData = Object.assign([...data.action], Object.assign([...data.action],{0:'id' }))
but this code is not working. The result I am looking for is:
{id:1 action: "user...}
You can just use the spread operator.
const newData = {
...data,
action: {
...data.action,
id: 1
}
};
If action is an array, you can try something like this:
const newAction = data.action.map((actionItem, index) => ({
...actionItem,
id: index + 1
}));
const newData = {
...data,
action: newAction
};

React - updating an object array in the state with setState

I'm working on a table planner app where guests can be assigned to dinner tables.
I have created an object array in the state called tabledata, which will contain objects like so:
this.state = {
tabledata: [
{
name: "Top Table",
guests: ["guest1", "guest2", "guest3"]
},
{
name: "Table One",
guests: ["guest3", "guest4", "guest5"]
}
]
}
I am then creating a drag and drop interface where guests can move between tables. I have attempted to update the state like so:
updateTableList (tablename, guest) {
const selectedTableObj = this.state.tabledata.filter((tableObj) => tableObj.name === tablename);
const otherTableObjs = this.state.tabledata.filter((tableObj) => tableObj.name !== tablename);
selectedTableObj[0].guests.push(guest);
const updatedObjectArray = [...otherTableObjs, selectedTableObj];
this.setState({
tabledata: [...otherTableObjs, ...selectedTableObj]
});
}
This works but because I am removing selectedTableObj from the state and then adding it to the end of the array I'm getting some funky results on screen. The updated table always goes to the bottom of the page (as you'd expect).
How can I update the object without changing its position within the array?
Find the index of the table you want to update using Array.findIndex(). Create a new tabledata array. Use Array.slice() to get the items before and after the updated table, and spread them into the new tabledata array. Create a new table object using object spread, add the updated guests array, and add the table object between the previous items:
Code (not tested):
updateTableList(tablename, guest) {
this.setState((prevState) => {
const tableData = prevState.tabledata;
const selectedTableIndex = tableData.findIndex((tableObj) => tableObj.name === tablename);
const updatedTable = tableData[selectedTableIndex];
return {
tabledata: [
...prevState.tabledata.slice(0, selectedTableIndex),
{
...updatedTable,
guests: [...updatedTable.guests, guest]
},
...prevState.tabledata.slice(selectedTableIndex + 1)
]
};
});
}
selectedTableObj[0].guests.push(guest) directly mutates the state which is not encouraged in React.
Try this:
this.setState((prevState) => {
const newData = [...prevState.tabledata];
// if you pass in `index` as argument instead of `tablename` then this will not be needed
const index = prevState.tabledata.findIndex(table => tableObj.name === tablename);
newData[index] = {
...newData[index],
guests: newData[index].guests.concat([guest]),
};
return { tabledata: newData };
});
You also did not remove the guest from its previous table so you need to modify for that.
You can do it with a Array.reduce
let newState = this.state
// let newState = {...this.state} // in case you want everything immutable
newState.tableData = newState.tableData.reduce((acc, table) =>
if(table.name === tableName) {
return acc.concat({...table, guests: table.guests.concat(newGuest)})
} else {
return acc.concat(table)
}
)

Categories