How to conditionally add variables to a destructuring assignment? - javascript

I am building a function to export a JSON object to an Excel file with SheetJS. Before exorting the data, I need to filter it to include only certain fields/properties. I have a .map() method with a destructuring assignment that takes a number of arguments. Each argument is a field that should be filtered out from the JSON data before generating the report. Some fields are always filtered out, like "favourite" or "linkedinsummary" in the code below. But other fields, like "comments" should be filtered out only if the user has decided to not include it. My problem is that I can't figure out how to conditionally add certain fields to the destructuring assignment. I have tried the following:
//this filters out all the fields that should not appear in the report
const filteredProfiles = transformedProfiles.map((profile) => {
const {
//Below: fields to filter out
favourite,
linkedinsummary,
...filteredProfile
} = profile;
const result = {...filteredProfile};
//if the user has decided that "comments" should not be included, then add it to the
list above
if (!store.state.userData.proptions.fields.comments) {
result.comments = profile.comments;
}
return result;
});
If I add "comments" to the list directly, it works, "comments" is left out. But with a conditional statement like above, "comments" is still exported to the report.

From the OP's last comment ...
"... How can I filter out the transformedProfiles array with a variable number of fields to exclude base on the user selection? For ex. favourite and linkedinsummary should always be filtered out. But comments should be filtered out only if store.state.userData.proptions.fields.comments is false." – jeff3546
... and from one of my above comments ...
_#jeff3546 ... Is this correct? ... if (!store.state.userData.proptions.fields.comments) {result.comments = profile.comments;} ... which generically translates to ... "Whenever fields does not have a certain property or the property's value is either false or otherwise falsy, it has to be assigned from profile to result." ... Or in other words ... "Whatever truthy property name is carried by fields, its related key/property has to be deleted from result."
In case the above is correct then the OP's provided example code changes to a generic implementation similar to the next provided one ...
const listOfDismissedKeys = Object
.entries(store.state.userData.proptions.fields)
//.filter(([key, value]) => value !== false)
//.filter(([key, value]) => value === true)
.filter(([key, value]) => !!value)
.map(([key]) => key);
const filteredProfiles = transformedProfiles
.map(profile => {
const { favourite, linkedinsummary, ...result } = profile;
listOfDismissedKeys
.forEach(key =>
Reflect.deleteProperty(result, key)
);
return result;
});

Related

How can the index of an object within an array be preserved when modifying an object property while using the spread operator?

I am having a React useState-Variable that stores an Array of Objects
which are of this Question type:
type Question = {
id: number,
text: string,
otherProps: string,
...... (and so on)
}
Example of my useState
const [questions, setQuestions] = React.useState<Question[]>([{id: 1, text: "hello?", otherProps: "Lorem Ipsum"}])
The order of these Question objects in the useState-Variable Array matters, so my question is: How should the following function be changed so that the text of the Question is changed but the array index of the modified object is maintained/kept?
I am aware that currently I am first deleting the object and then placing a newly created on at the end, but I can't figure out another way right now.
function setQuestionTextById(id:number, text:string) {
if (!questions) return;
const question:Question|undefined = questions.find(x => x.id === id);
if (!question) return;
const newQuestion: Question = {
...question,
text,
};
const filteredQuestions = questions.filter(item => item.id !== id);
setQuestions([...filteredQuestions, newQuestion]);
}
You should use map on the array with a function that checks if the id is the one you want - if so it modifies it with the new text, otherwise leaves it as is.
This way, your whole function becomes:
function setQuestionTextById(id:number, text:string) {
const updatedQuestions = questions.map(question => question.id === id ? { ...question, text } : question);
setQuestions(updatedQuestions);
}
Which I find much more readable than the original, as well as preserving order as you say you need.
One further refinement would be to use the functional update form of setQuestions so it doesn't depend on the previous value of questions, but I'll leave that up to you - it may not matter, depending on how this is being used in your component.

How to update a value in destructing and loop

What I try to achieve:
I want to update a value in an obj, which is part of the element of array. See the code below will give you better idea.
There is an issue that I update the value of object, via reference, instead of making a copy. This causes the state behave strangely.
I try to change it to making a copy, but I am not sure.
e.g.
const returnObj = {
...objs,
fields: [{name, value}, {name, value}, {name, value_update_this_only}, ...],
};
// This is the current code
export function* onChange(action) {
// get partial state from redux state
const list = yield select((state) => state.list);
let objs = list[action.index];
// * e.g. objs.fields === [{name, value}, {name, value}, ...]
// * basically following, find the correct field and update its value
// * following has problem, beause we change the value of a reference,
// * instead we should make a new copy, so redux can react
objs.fields.map((field) => {
if (field.name === action.fieldName) {
field["value"] = action.fieldValue;
}
return field;
});
// fire to redux reducer
yield put({
type: "UPDATE",
prop: obj,
docIndex: action.index,
});
}
// the problem: I don't know how to do it in destructing manner.
const returnObj = {
...objs,
fields: [],
};
I think rather than try and come up with a single destructuring statement that makes this work, it's easier to digest (and arguably more readable) in smaller steps:
Make a shallow copy of objs; call it copy for now
Recreate fields array and every item within it
For the desired array item, update its value
Set the copy.fields to the array created in 2
// Step 1: Shallow copy
let copy = { ...objs }
// Step 2: Recreate fields and every item
let fields = copy.fields.map((field) => ({
...field
}))
// Step 3: Update value of desired item
fields.forEach((field) => {
if (field.name === action.fieldName)
field.value = action.fieldValue
})
// Step 4: Reassign fields to the copy
copy.fields = fields
Refactoring this, steps 2-4 can be combined into one step without sacrificing that much readability:
let copy = { ...objs }
copy.fields = copy.fields.map((field) => ({
...field,
value: field.name === action.fieldName ? action.fieldValue : field.value,
}))
It's been a long time since I've used redux or sagas, so I'm not sure whether fields needs to be an entirely new array or if just the changed object within fields needs to be new, but the above can be modified to accommodate either need.

Shorter way to update object properties in JavaScript?

I have a list of user submissions on different questions and whenever it is being updated, I have several properties I'm updating. I'm wondering if there is a better way perhaps by destructing or spreading to make this cleaner/shorter? Here's what I'm doing at the moment, I've lessened the number of properties in the example, but in the actual project I am updating around 5-6 properties one by one and I felt it is a little repetitive to set it one by one.
updateSubmission ( id, type, value ) {
const obj = state.submission.filter( el => el.id === id )[ 0 ]
obj.type = type
obj.value = value
}
Use .find to find the single matching object instead of .filter, then you can Object.assign both properties with shorthand:
updateSubmission ( id, type, value ) {
Object.assign(
state.submission.find( el => el.id === id ),
{ type, value }
);
}
If you can change the signature of updateSubmission then you can also make the code more generic.
updateSubmission ({ id, ...rest }) {
let obj = state.submission.find( el => el.id === id );
obj = { ...obj, ...rest };
}
Usage:
updateSubmission({id:'123', type:'abc', value:'xyz'})
Future Benefit:
If tomorrow, the sequence of params changes? then the code works without changing signature.
It doesn't matter how many argument you had earlier, with object destructuring, the code works without adding arguments to signature.

Locating object in array by id and changing its values based on variable parameters: Why does my code work?

The problem isn't the code, it's that I don't understand why what I have works, although it does what I need it to do. I'm building an app that keeps track of jobs. The jobs, each an object, are stored in an array in a JSON file. I'm adding the functionality to edit a job's key/value pairs in the JSON file.
Anyway, my function editJob takes in an object as an argument that has an id and a variable amount of other properties. The goal is then to locate the job in JSON that matches the id, then update that job's properties based only on the editItems object.The code below allows for that. I just don't understand the line below the Object.keys code. Why would I not compare the located job's keys to the editItems keys?
I don't know why it works and am worried it will break at some point because it's not properly coded.
function editJob (editItems) {
// editItems is an object like this: ex. { id: 3, customer: 'Artemis', source: 'Google', description: 'Fixed toilet' }
return this.jobs.map(job => {
let editedJobs = Object.assign({}, job);
if (editedJobs.id === editItems.id) {
Object.keys(editItems).forEach(k => {
if (editedJobs[k] === job[k]) { // WHY DOES THIS WORK. why job[k] and not editItems[k]???
editedJobs[k] = editItems[k];
}
});
}
return editedJobs;
});
}
Since you just did editedJobs = Object.assign({}, job), the expression editedJobs[k] === job[k] will be true for every k. You can just omit it. You would achieve the same thing by
function editJob (editItems) {
return this.jobs.map(job => {
return job.id === editItems.id
? Object.assign({}, job, editItems)
: job;
});
}

Role hierarchy mapper / generator (recursive)

I would like to create an object of arrays converting the single level key - (string) value relation to key - (array) keys collection.
Basically, the code must collect other keys and their values recursively starting from collecting self. At the end the object must be like this;
{
ROLE_SUPER_ADMIN: [
'ROLE_SUPER_ADMIN',
'ROLE_ADMIN',
'ROLE_MODERATOR',
'ROLE_AUTHOR'
]
}
What i have achieved yet is;
export const roles = {
ROLE_SUPER_ADMIN: 'ROLE_ADMIN',
ROLE_ADMIN: 'ROLE_MODERATOR',
ROLE_MODERATOR: 'ROLE_AUTHOR',
ROLE_AUTHOR: null,
ROLE_CLIENT: null
}
export function roleMapper() {
const roleArray = {}
const mapper = (key) => {
roleArray[key] = [key];
if (!roles[key] || Array.isArray(roles[key])) {
return;
} else if (!roles[roles[key]]) {
roleArray[key].push(roles[key])
} else {
if (roleArray.hasOwnProperty(key)) {
Object.keys(roles).filter(r => r !== key).forEach((role) => {
roleArray[key].push(mapper(role))
})
}
}
}
Object.keys(roles).forEach((key) => {
mapper(key)
});
console.log(roleArray);
}
I have completely lost solving this. Please help, thanks.
I would use a function generator for this, taking advantage of the easy recursion approach and taking advantage of Object.entries combined with Array.map.
The below method acquires all the siblings of a defined key from an object, assuming that each key value may be the child of the said key.
As a side note, you could technically do that in many other ways (without relying on function generators), I just think that the generator approach is clever and easier to maintain. Moreover, it allows you to re-use the method later and allows you to eventually iterate the values.
Code explanation is directly in the code below.
const roles = {
ROLE_SUPER_ADMIN: 'ROLE_ADMIN',
ROLE_ADMIN: 'ROLE_MODERATOR',
ROLE_MODERATOR: 'ROLE_AUTHOR',
ROLE_AUTHOR: null,
ROLE_CLIENT: null
}
// Acquire all the siblings, where a sibling is a key whose value is the value of another key.
function* getSiblings(v, source) {
// if the desired key exists in source..
if (source[v]) {
// yield the value, which is a role in that case.
yield source[v];
// next, yield all the siblings of that value (role).
yield* [...getSiblings(source[v], source)];
}
}
// Map all roles by its siblings.
const res = Object.entries(roles).map(([key, role]) => {
// key is the main role, whereas role is the "child" role.
// Technically, [key] is not exactly a child role of [key], so we're injecting it manually below to avoid polluting the getSiblings method.
return {
[key]: [key, ...getSiblings(key, roles)] // <-- as mentioned above, the array is build by starting from the main role (key) and appending the child roles (siblings). [key] is a shorthand to set the key.
}
});
console.log(res);
I would separate out the recursive call necessary to fetch the list from the code that builds the output. That allows you to make both of them quite simple:
const listRoles = (rolls, name) => name in roles
? [name, ... listRoles (rolls, roles [name] )]
: []
const roleMapper = (roles) => Object .assign (
... Object.keys (roles) .map (name => ({ [name]: listRoles (roles, name) }))
)
const roles = {ROLE_SUPER_ADMIN: 'ROLE_ADMIN', ROLE_ADMIN: 'ROLE_MODERATOR', ROLE_MODERATOR: 'ROLE_AUTHOR', ROLE_AUTHOR: null, ROLE_CLIENT: null}
console .log (
roleMapper (roles)
)
Here listRoles is the recursive bit, and it simply takes a roles object and a name and returns all the descendant names, so
listRoles(roles, 'ROLE_MODERATOR') //=> ["ROLE_MODERATOR", "ROLE_AUTHOR"]
roleMapper uses that function. It takes the roles object and calls listRoles on each of its keys, combining them into a new object.
Together, these yield the following output:
{
ROLE_SUPER_ADMIN: ["ROLE_SUPER_ADMIN", "ROLE_ADMIN", "ROLE_MODERATOR", "ROLE_AUTHOR"],
ROLE_ADMIN: ["ROLE_ADMIN", "ROLE_MODERATOR", "ROLE_AUTHOR"],
ROLE_MODERATOR: ["ROLE_MODERATOR", "ROLE_AUTHOR"],
ROLE_AUTHOR: ["ROLE_AUTHOR"],
ROLE_CLIENT: ["ROLE_CLIENT"]
}
I see the accepted answer generates a structure more like this:
[
{ROLE_SUPER_ADMIN: ["ROLE_SUPER_ADMIN", "ROLE_ADMIN", "ROLE_MODERATOR", "ROLE_AUTHOR"]},
{ROLE_ADMIN: ["ROLE_ADMIN", "ROLE_MODERATOR", "ROLE_AUTHOR"]},
{ROLE_MODERATOR: ["ROLE_MODERATOR", "ROLE_AUTHOR"]},
{ROLE_AUTHOR: ["ROLE_AUTHOR"]},
{ROLE_CLIENT: ["ROLE_CLIENT"]}
]
(The difference is that mine was a single object, versus this one which was an array of single-property objects.)
While that feels less logical to me, it would be even easier to write:
const roleMapper = (roles) => Object.keys (roles) .map (n => ({ [n]: listRoles (roles, n) }))

Categories

Resources