Update a property from a string to object on JavaScript without mutation - javascript

When iterating through an object and updating it I feel that I am not doing it right. This is the method I have built to do it and I have doubts about record[header] = {label: record[header]};
Does anybody have a better solution to avoid mutation records array?
setAfterRecords(preview: any): void {
const { columns: headers } = preview;
const { rows: records } = preview;
records.map((record, i) => {
headers.forEach(header => {
record[header] = {
label: record[header],
};
});
return record;
});
this.after = { headers, records };
}
Thank you.

The following should do it. It uses the new-ish object spread operator (... on {}). It's like Object.assign() but in a more concise syntax.
setAfterRecords(preview: any): void {
const { columns: headers, rows: records } = preview;
// Build a new array of records.
const newRecords = records.map(record => {
// Build a new record object, adding in each header.
return headers.reduce((c, header) => {
// This operation shallow-copies record from the initial value or
// previous operation, adding in the header.
return { ...c, [header]: { label: record[header] } }
}, record)
})
this.after = { headers, records: newRecords };
}

Related

Edit function not saving changes to state data in React

I am trying to provide functionality in my webpage for editing state data.
Here is the state structure
state = {
eventList:[
{
name: "Coachella"
list: [
{
id: 1,
name: "Eminem"
type: "rap"
}
{
id: 2,
name: "Kendrick Lamar"
type: "rap"
}
]
}
]
}
I want to be able to edit the list arrays specifically the id, name, and type properties but my function doesn't seem to edit them? I currently pass data I want to override id name and type with in variable eventData and an id value specifying which row is selected in the table which outputs the state data.
Here is the function code:
editPickEvent = (eventData, id) => {
const eventListNew = this.state.eventList;
eventListNew.map((event) => {
event.list.map((single) => {
if (single.id == id) {
single = eventData;
}
});
});
this.setState({
eventList: eventListNew,
});
};
When I run the code the function doesn't alter the single map variable and I can't seem to pinpoint the reason why. Any help would be great
edit:
Implementing Captain Mhmdrz_A's solution
editPickEvent = (eventData, id) => {
const eventListNew = this.state.eventList.map((event) => {
event.list.map((single) => {
if (single.id == id) {
single = eventData;
}
});
});
this.setState({
eventList: eventListNew,
});
};
I get a new error saying Cannot read property list of undefined in another file that uses the map function to render the state data to the table?
This is the part of the other file causing the error:
render() {
const EventsList = this.props.eventList.map((event) => {
return event.list.map((single) => {
return (
map() return a new array every time, but you are not assigning it to anything;
editPickEvent = (eventData, id) => {
const eventListNew = this.state.eventList.map((event) => {
event.list.forEach((single) => {
if (single.id == id) {
single = eventData;
}
});
return event
});
this.setState({
eventList: eventListNew,
});
};
const editPickEvent = (eventData, id) => {
const updatedEventList = this.state.eventList.map(event => {
const updatedList = event.list.map(single => {
if (single.id === id) {
return eventData;
}
return single;
});
return {...event, list: updatedList};
});
this.setState({
eventList: updatedEventList,
});
}
Example Link: https://codesandbox.io/s/crazy-lake-2q6ez
Note: You may need to add more checks in between for handling cases when values could be null or undefined.
Also, it would be good if you can add something similar to the original data source or an example link.
Turns out primitive values are pass by value in javascript, which I didn't know and why the assignment wasn't working in some of the previous suggested answers. Here is the code that got it working for me:
editEvent = (EventData, id) => {
const eventListNew = this.state.eventList.map((event) => {
const newList = event.list.map((single) => {
return single.id == id ? EventData : single;
});
return { ...event, list: newList };
});
this.setState({
eventList: eventListNew,
});
};

What is the best way for reducing multiple if statement?

I have a helper function that builds object with appropriate query properties. I use this object as a body in my promise request. What is the most elegant way for refactoring multiple if statements? Here is a function:
getQueryParams = (query, pagination, sorting) => {
let queryParam = {}
if (pagination && pagination.pageNumber) {
queryParam.page = `${pagination.pageNumber}`
}
if (pagination && pagination.rowsOnPage) {
queryParam.size = `${pagination.rowsOnPage}`
}
if (query) {
const updatedQuery = encodeURIComponent(query)
queryParam.q = `${updatedQuery}`
}
if (sorting) {
queryParam.sort = `${sorting.isDescending ? '-' : ''}${sorting.name}`
}
return service.get(`/my-url/`, queryParam).then(result => {
return result
})
}
If service checks its parameters (as it should), you could benefit from the default parameters. Something like this:
const getQueryParams = (
query = '',
pagination = {pageNumber: 0, rowsOnPage: 0},
sorting = {isDescending: '', name: ''}
) => {
const queryParam = {
page: pagination.pageNumber,
size: pagination.rowsOnPage,
q: encodeURIComponent(query),
sort: `${sorting.isDescending}${sorting.name}`
}
return ...;
};
A live example to play with at jsfiddle.
This is an idea how it could looks like, but you need to adopt your params before:
const query = new URLSearchParams();
Object.keys(params).forEach(key => {
if (params[key]) {
query.append(key, params[key]);
}
});

Reactjs push new element to nested state

I'm new to javascript and react, I try to push a new element to an array inside the state but there's no success.
state = {
columns: [
{
id: 122,
items: [{text:'abc'},{text:'cde'}]
},
{
id: 143,
items: []
}
]
}
addItem(columnId,text) {
const newItem = {text: text}
//this.setState(...)
}
Basically, I have an addItem function with given columnId and some text content, I want to push a new item to the items array inside the column with given columnId.
I heard that it'd be much easier with the help of immutability-helper, is that right?
You don't need any immutability helper if you learn methods like map, filter and spread syntax or Object.assign. Using some of them (the suitable ones) you can do whatever you want without mutating your state.
const addItem = (columnId, text) => {
// We are mapping the columns from the state.
const newColumns = this.state.columns.map(column => {
// If id does not match just return the column.
if (column.id !== columnId) return column;
// Else, return a new column object by using spread syntax.
// We spread the column (preserve other properties, and create items again
// using spread syntax. Spread the items, add the new text object.
return { ...column, items: [...column.items, { text }] };
});
// Lastly, set the state with newColumns.
this.setState({ columns: newColumns });
};
Without comments:
const addItem = (columnId, text) => {
const newColumns = this.state.columns.map(column => {
if (column.id !== columnId) return column;
return { ...column, items: [...column.items, { text }] };
});
this.setState({ columns: newColumns });
};
You can get value from state and push to that.
And this.setState makes re-rendering.
addItem(columnId, text) {
const newItem = {text};
let columns = this.state.columns;
let findColumn = columns.find(({id})=>id === columnId);
if( findColumn ) {
findColumn.items.push( newItem );
}
else {
columns.push({id:columnId, items:[newItem]});
}
this.setState({columns});
}
If you want tight. We can use destructuring.
addItem(columnId, text) {
let {columns} = this.state;
let findColumn = columns.find(({id})=>id === columnId);
if( findColumn ) {
findColumn.items.push( {text} );
}
else {
columns.push({id:columnId, items:[{text}]});
}
this.setState({columns});
}
You can create a copy of the state and modify it:
addItem(columnId,text) {
let newColums = [...this.state.columns]; // Create a copy of the state using spread operator
newColumns[columnId].items.push({text: text}) // Add the new item
this.setState({columns:newColumns}) // Set the state
}
addItem(columnId,text) {
const { columns } = this.state;
let newItem = columns.find( column => column.columnId === columnId);
if(newItem) {
newItem = {
...newItem,
text: text
}
} else {
newItem = {
columnId: columnId,
text: text
}
}
const newColumns = [ ...columns, newItem]
this.setState({ columns: newColumns })
}

Load data in the object?

I am not sure if i'm doing the right approach, I am doing like class style. Is there a way to load data in the object using loadProducts(data) so then I can call orderLines.getItemsType()
const orderProducts = {
loadProducts: function(data) {
//Load data into orderProducts object?
},
getItemsType: function(type) {
// return data
}
};
Usage:
const items = orderProducts.getItemsType(['abc', 'ddd']);
Note: It is for node.js, not for the browser.
First you want to save the products into a property. We will load the property with some dummy data.
We can then filter the data using filter and test if the item is in the products array like this:
const orderProducts = {
// The list of products
products: [],
// The products to load
loadProducts: function(...data) {
this.products.push(...data)
},
// Get items passed in
getItemsType: function(...type) {
return this.products.filter(p => type.includes(p))
}
}
orderProducts.loadProducts('abc', '123', '111', 'ddd')
const items = orderProducts.getItemsType('abc', 'ddd')
console.log(items)
I guess next approach can help you to make it class approach and solving your question:
class OrderProducts {
constructor(data) {
this.data = data;
this.getItemsType = this.getItemsType.bind(this);
}
getItemsType(type) {
// return the data filtering by type
return this.data;
}
}
// usage
const orderProduct = new OrderProduct(data);
const items = orderProduct.getItemsType(['abc', 'ddd']);

Return object from observable inside another observable

I try to fetch data from 3 different REST end points. The data model consists of main data and has advanced Array with 2(might be more in the future) Objects. I want to inject an Array with options into each of advanced Objects, based on REST endpoint specified in each of them. Everything works and is returned from Observable as Object, except appended options, that come as Observables.
Simplified data:
{
simple: {
param: "searchQuery",
},
advanced: [
{
param: "food",
optionsModel: {
url: "http://address.com/food",
}
},
{
param: "drinks",
optionsModel: {
url: "http://address.com/drinks",
}
}
]
}
food and drinks have the same structure consisting of Objects with name and id:
{
data: [
{
name: "DR1",
id: 1
},
{
name: "DR2",
id: 1
},
...
]
}
In my data model I don't have options[] array, so I inject it manually. Service:
searchConfiguration$: Observable<SearchConfiguration> = this.http
.get(this._configURL)
.map((config) => {
let configuration = config.json();
let advancedArray = configuration.advanced;
if(advancedArray) {
advancedArray.forEach(advanced => {
advanced.options = [];
advanced.options = this.http.get(advanced.optionsModel.url)
.map((r: Response) => r.json().data
})
}
return configuration;
})
Parent component:
getSearchConfig() {
this.searchConfiguration$ = this._bundlesService.searchConfiguration$;
}
Then I have async pipe in the html to subscribe to Observable. How can I get my options appended to advanced as actual Arrays and not as a stream?
Edit - Solution
Thanks to martin's answer, the solution is to flatten both streams and connect them in the end with forkJoin()
searchConfiguration$: Observable<SearchConfiguration> = this.http
.get(this._configURL)
.map((config) => config.json())
.concatMap((configuration) => {
let observables = configuration.advanced.map((advanced) => {
return this.http.get(advanced.optionsModel.url)
.map((r: Response) => r.json().data)
.concatMap((options) => advanced.options = options)
});
return Observable.forkJoin(observables, (...options) => {
return configuration;
})
});
I didn't test it but I think you could you something as follows:
searchConfiguration$: Observable<SearchConfiguration> = this.http
.get(this._configURL)
.map((config) => config.json())
.concatMap(configuration => {
var observables = configuration.advanced.map(advanced => {
return this.http.get(advanced.optionsModel.url)
.map((r: Response) => r.json().data);
});
return Observable.forkJoin(observables, (...options) => {
configuration.options = options;
return configuration;
});
})

Categories

Resources