Table don't change after state change - reactjs

I have antd table, I am to try to update only a subtable to add one row after api return.
If subtable is empty the row it shows on the table, if subtable have one row and more table subtable don't change. The problem is with expandedRowRender if i click for sorting the new data it shows there
But i cannot make a table to add the new row. When i print the new state for data its ok, i can see the new row there, but the table is the same. Any ideas ? i am not sure if the problem is the setState or something else i am doing wrong.
for (const property in dataGroup) {
if (dataGroup[property].id === new.group_id) {
dataGroup[property].group.push({
id: user.id,
name: user.name,
});
break;
}
}
this.setState({
data: [...dataGroup],
});
console.log(this.state.data);
-------------------------
return (
<Table
columns={columns}
rowSelection={rowSelection}
rowKey={(record) => record.id}
expandable={{ expandedRowRender }}
dataSource={data}
/>
........
Table json
[
{
"id": 97,
"name": "f",
"description": "",
"group": [
{
"id": 90,
"name": "fds",
},
{
"id": 91,
"name": "dsa",
},
]
},
{
"id": 98,
"name": "fddf",
"description": "",
"group": [
{
"id": 96,
"name": "fddd",
},
]
}
]

First of all, never use setState inside a loop or multiple times inside the same execution path because it is useless and restricts performance. call it at the end of your function execution.
The reason your state is not changing is because react thinks your state.data variable hasn't changed since it maintains the same reference. You need to use the spread operator to recreate the same object with a different reference.
for (const property in data) {
if (data[property].id === new.group_id) {
data[property].group.push({
id: user.id,
name: user.name,
});
break;
}
}
this.setState({
data: { ...data },
});
console.log(this.state.data);
Also make sure to set the key prop for your component arrays

Related

Angular - Convert objects in a nested array into comma seapared values before binding to grid

below is part of my JSON response coming from an API
{
"totalCount": 2,
"customAttributes": [
{
"objectType": "OWNER",
"atrributeId": 215,
"attributeName": "DATELICENSEFIRSTISSUED",
"attributeDisplayName": "DATE LICENSE FIRST ISSUED",
"dataType": "DATE",
"inputValues": [],
"isGridEligible": "true",
"isInvoiceEligible": "false"
},
{
"objectType": "LOCATION",
"atrributeId": 217,
"attributeName": "DONOTRENEW",
"attributeDisplayName": "DO NOT RENEWS",
"dataType": "Value List",
"inputValues": [
{
"id": 5,
"value": "VEHICLELISTREQUIRED"
},
{
"id": 6,
"value": "STATESWITHRECIPROCITY"
}
],
"isGridEligible": "true",
"isInvoiceEligible": "false"
}
]
}
Here, I am binding customAttributes as grid data.
this.customFieldsService.getCustomFields(this.columnList, this.pageNumber, this.pageSize, null).subscribe(res => {
if(res){
this.cfData = res;
this.gridData = {
data: this.cfData.customAttributes,
total: this.cfData.totalCount
}
}
});
Here, my problem is with inputValues column, which comes as an array of objects. I need to convert it to comma seaparated values and then bind to grid data like
"inputValues": ["VEHICLELISTREQUIRED" "STATESWITHRECIPROCITY"]
I can ignore the "id" property as we are not using it at angular side. I tried using join method but not able to solve it within the nested array. Please suggest. Thanks.
In typescript it can be done with:
const joined: string = customAttribute.inputValues
.map(x => x.value) // [{value: 'VEHICLELISTREQUIRED'}, {value: 'STATESWITHRECIPROCITY'}]
.join(' ') // "VEHICLELISTREQUIRED" "STATESWITHRECIPROCITY"
const putIntoArray = [joined]; // ["VEHICLELISTREQUIRED" "STATESWITHRECIPROCITY"]
Of course you can put the joined string immediately into an array.

I need to get a nested element in a json array

There is some list of elements in json format, it looks like this:
[{
'id': 0,
"name": "Category name1",
"services": [{
"id": 0,
"name": "Product name1"
}, {
"id": 1,
"name": "Product name2"
}]
},
{
'id': 1,
'name': "Category name2",
"services": [{
"id": 0,
"name": "Product name1"
}, {
"id": 1,
"name": "Product name2"
}]
}
]
I'm trying to get only the entire "services" array from the first category. Conditionally, I'm trying to get it as follows:
this.class = this.http.get('/assets/products.json');
this.class.forEach(element => {
if (element.id == ID) //The ID is obtained when calling the function in which this code is executed
{
console.log(element.services);
}
}
However, this gives me absolutely nothing and "undefined" is output to the console, however, with the same array and under the same conditions on the site https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach it (foreach and console.log) outputs everything I need.
//The same foreach only on the mozilla website
array1.forEach(item => {
if(item.id==1){ //1 вместо ID
console.log(item.services);
}});
Output to the console: Array [Object { id: 0, name: "Product name1"}, Object { id: 1, name: "Product name2"}].
P.S. I don't really need this list of products in the console, I'm trying to pass a variable, but since undefined is passed to the variable, as well as to the console, I can't use the variable and call it to display products on the page. All this is used in the typescript angular project.
The HttpClient methods like get return observables, to which you need to subscribe to in order for the request to even get executed. In your situation, the class property is only holding a reference to the observable returned by calling this.http.get. Try subscribing to the observable and use the result to extract the data that you need.
this.http.get<any[]>('/assets/products.json').subscribe((data) => {
this.class = data;
this.class.forEach((element) => {
if (element.id == ID) {
console.log(element.services);
}
});
});

How to put a loader in a nested table in antd?

I have a nested table in antd, wherein when I click on a row, it expands and a new table opens open. Illustrated here.
But it takes some time to fetch the data for the nested table ( I fetch it through an API). I need to put a loader/spinner in the nested table to indicate that the data is still not available to displayed. How can I achieve that in antd?
I have tried doing the following, but it didnt work:
//(Outer table)
<Table key="campaignListByDate-table"
columns={campaignDateColumns}
expandable={{ expandedRowRender }}
loading={{
indicator: <div><Spin /></div>,
spinning: !campaignList
}}
dataSource={campaignList}
/>
//Inner table ( The one that opens when u click on the "+" sign of a row.
<Table
columns={columnsExpanded}
dataSource={emailRate}
pagination={false}
loading={{
indicator: <div><Spin /></div>,
spinning: !emailRate
}}/>
CampaignList:
[
{
"id": "27813f63-aee2-4c69-bf5d-9e4ac8",
"name": "bnce",
"templateId": "ae7e094f-1735-4a31-bc67-95bd3d",
"userId": "3122be78-703d-4621-92f0-8a2bd8",
"createdAt": 1604984929337,
},
{
"id": "438e0cd9-a550-453a-8a5b-4bd37",
"name": "asd",
"templateId": "ae7e094f-1735-4a31-bc67-95bd3",
"userId": "3122be78-703d-4621-92f0-8a2bd87",
"createdAt": 1604985347370,
},
]
EmailRate:
[
{
"time": "2020-11-11 11 : 39",
"count": 3,
"key": "2020-11-11 11 : 39"
}
]
From your code, I assume you are trying to set ![ ] to spinning when you do not have any data. But ![ ] will not return true.
Example:
//When you have some data
console.log(!['something to avoid empty array'])
//when you do not have any data
console.log(![])
Both cases will return false and your loader is not getting activated.
Try using boolean state variables and assign true and false to those variables before and after your data fetch and assign it to the table spinner

Update single record in nested state object, react-redux

I am working on a grid structure where user can add sections, sub-sections or items dynamically. I am managing that things in my redux state object. UI of my grid is as following :
I want to update a single row record instead of reloading whole grid again. For that, whenever user changes any cell value of row i am calling update-row api and on success of that i am trying to update that value in reducer using following code.
case UPDATE_ORDER_LINES_SUCCESS:
let stateData = state.get(`GridData`);
const dataIndex = stateData.children.findIndex(
(listing) => listing.id === action.row.id // row id which is updated
);
stateData[0].children[dataIndex] = action.row;
let data = Object.assign(stateData, { children: stateData.children });
state = state.set(`GridData`, [data]);
This code is working fine for first level of children records (as per json object) but problem occur if user update value of nth level children record. How can i update that row record in my redux state ?
My current redux state sample is :
{
"views": [
{
"id": "5e6b8961ba08180001a10bb6",
"viewName": "house",
"description": "house view",
"name": "house",
"children": [
{
"id": "5e6b8961ba08180001a10bb7",
"viewName": "house",
"sectionName": "Temporary",
"sectionId": "SEC-02986",
"description": "Temporary",
"sequenceNumber": 4,
"refrenceId": "SEC-02986",
"children": [
{
"id": "5e590df71bbc71000118c109",
"lineDescription": "AutoPickPack01",
"lineAction": "Rent",
"quantity": 5,
"deliveryDate": "2020-02-29T06:00:00+11:00",
"pickDate": "2020-02-28T06:00:00+11:00",
"pickupDate": "2020-03-01T06:00:00+11:00",
"prepDate": "2020-02-28T06:00:00+11:00",
"returnDate": "2020-03-01T06:00:00+11:00",
"shippingDate": "2020-02-29T06:00:00+11:00",
"unitPrice": 7000,
"children": [
{
"id": "5e590df71bbc71000118c10a",
"orderId": "Ord-05788_1",
"lineNumber": "01a7b77c-792a-4edb-9b73-132440621968",
"purchaseOrderNumber": null,
"lineDescription": "29Janserial",
"lineAction": "Rent",
"quantity": 5,
"pricingMethod": "Fixed",
"displayUnit": "Days",
"unitPrice": 0,
"chargeAmount": 0,
"pickDate": "2020-02-17T06:00:00+11:00",
"prepDate": "2020-02-28T06:00:00+11:00",
"shippingDate": "2020-02-29T06:00:00+11:00",
"deliveryDate": "2020-02-29T06:00:00+11:00",
"pickupDate": "2020-03-01T06:00:00+11:00",
"returnDate": "2020-03-01T06:00:00+11:00",
"name": "29Janserial",
"description": "29Janserial",
"discountAmount": "",
"discountPrice": ""
}
]
}
]
}
]
}
]
}
What is the best way to update nested children row data in reducer ?
As redux doesn't allow to mutate the current state and return it back, it's hard to modify a nested child. Although its highly discouraged to
have this kind of nested structure in redux, rather it should be normalized as #bsapaka answered. But if you still want to update the nested
object and return the whole state as an immutable one, immer should be your friend. immerJS has been so popular for handling immutable states.Although
Install immer and redux-immer in your case
yarn add immer redux-immer
In your reducers.js file where all reducers have been combined using combineReducers
import produce from 'immer';
import { combineReducers } from 'redux-immer';
// Replace your current combineReducers with
combineReducers(produce, { /* Object of all reducers */ });
In your current reducer file
import product from 'immer';
const findNestedChild = (arr, itemId) => (
arr.reduce((a, item) => {
if (a) return a;
if (item.id === itemId) return item;
if (item['children']) return findItemNested(item['children'], itemId)
}, null)
);
case UPDATE_ORDER_LINES_SUCCESS:
return produce(state, draftState => {
const { row: newChild, row: { id }} = action;
let child = findNestedChild(draftState.views, id);
child = newChild;
});
You should normalize your state, which flattens the tree, and entities become associated by id references instead of direct nesting.
For example
{
"entities": {
"orders": {
"o1": { "id": "o1", "productIds": ["p1", "p2"] },
"o2": { "id": "o2", "productIds": ["p2", "p3"] },
"o3": { "id": "o2", "productIds": ["p3"] }
},
"products": {
"p1": { "id": "p1", "orderIds": ["o1"] },
"p2": { "id": "p1", "orderIds": ["o1", "o2"] },
"p3": { "id": "p1", "orderIds": ["o2", "o3"] }
},
"views": {
"v1": { "id": "v1", "childIds": ["v1.1", "v1.2"] },
"v1.1": { "id": "v1.1", "parentId": "v1" },
"v1.2": { "id": "v1.2", "parentId": "v1" }}
},
"ids": {
"orders": ["o1", "o2", "o3"],
"products": ["p1", "p2", "p3"],
"views": ["v1", "v1.1", "v1.2"]
}
}
There's more upfront work of finding the correct model and transforming the raw data into it, but you save a lot of time not having to deal with updates that are nested or affect multiple areas of data.
Redux docs on normalizing
A (de)normalization transformation tool
A reducer utility library to manage normalized state

How to access array of objects subelement from redux reducers

I have a response from an API like this one:
[{
"id": 1,
"name": "Microsoft",
"status": true,
"consoles": [{
"id": 4,
"name": "Xbox",
"status": true,
"subconsoles": [{
"id": 7,
"name": "Xbox 360",
"status": true,
"subconsoles": []
},
{
"id": 90,
"name": "Xbox One",
"status": false,
"subconsoles": [{
"id": 21,
"name": "Xbox One S",
"status": true,
"subconsoles": []
},
{
"id": 12,
"name": "Xbox One X",
"status": false,
"subconsoles": [{
"id": 41,
"name": "Xbox One X model 1",
"status": false,
"subconsoles": []
}]
}]
}]
}]
}]
Here is also the data in nice format:
What I am trying to achieve is to modify the status of the subconsoles.
So from layout section I pass the ID of what I want to change, but I am really stuck on how to access the subelement (and eventually the sub-subelement) in the reducers of Redux:
case SUBCONSOLES_ENABLE:
return {
...state,
id: action.payload.subconsoleId,
.....
}
You would need to create a copy of each of the arrays you have in your state before you can change them, to keep your state immutable. With so many levels, that would get complicated fast!
case SUBCONSOLES_ENABLE:
let newState = [...state]; //create copy of state array
..... //find the index of the object in the newState array which you would want to change
let newConsoles = [...newState[index].consoles]; //create a copy of consoles
..... //find the index of the object in the newConsoles array which you would want to change
let newSubConsoles = [...newConsoles[index2].subconsoles];//create a copy of subconsoles
let subConsole = newSubConsoles.find((a)=>a.id===action.payload.subconsoleId); //find the object in the newConsoles array which you would want to change
let newSubConsole = {...subConsole} //make a copy of your object
..... //make your changes to the copy
//replace the old object with the new object in newSubConsoles
//replace subConsoles array with newSubConsoles array in newConsoles
// replace consoles array with newConsoles array in new State.
//and finally!!
return newState;
Based on the shape of your state, I would suggest looking at normalizing your state and using Immutable.js
Check out the update fn from the 'immutability-helper' library, its recommended in the react docs themselves.

Resources