Shorter way to update object properties in JavaScript? - 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.

Related

How to conditionally add variables to a destructuring assignment?

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;
});

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.

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) }))

Remove function in React with .filter

While building a Todo app, I want to filter out an object out of my array with a remove function. So far I got this.
deleteTask(task) {
let taskList = this.state.tasks;
var newTask = taskList.filter(function(_task) { return _task != task})
this.setState({
tasks: newTask
});
}
Only problem is, the function returns the whole array while using the function.
So the Task argument that should return just an object out of my array returns the whole array instead while in my newTask var.
How can I bind or make this function work?
The array which I am wanting to remove an object from is not located in the same Component, dont know if that matters. But for extra info.
First off, let's see why _task != task doesn't work as you need. Try this:
const a = { x: 10, y: 'hello' };
const b = { x: 10, y: 'hello' };
console.log(
a==b,
a===b,
Object.is(a,b)
);
Suprising, eh? Read this for more details.
Anyway, you should refactor your code to include an id property in your tasks, so that you can compare two tasks with their ids - no need to worry about weird implementations of object comparisons in JavaScript!
This should then work:
deleteTask(taskId) {
this.setState(prevState => ({
tasks: prevState.tasks.filter(task => task.id !== taskId)
}));
}
Equality operator in javascript compares the references of the object. So even if both object have same value, since they point to different instances, == will always return false. So as I see you have two options:
Refactor your code to include a id part and which that to compare two tasks.
Use some library like lodash or underscore for deep comparison

ES6 copy properties to target only if they already exist

Essentially what I want
let schema = {
name: null,
lastname: null
}
let values = {
name: "John",
unwanted: "haxor"
}
to end up in:
console.log(sanitized); // {name: "John", lastname: null}
--
Using Object.assign(schema, values) ends up with the unwanted value.
Is there a simple way?
Edit: I should add, this is to avoid using a library like lodash or underscore, so if the solution is overly complex, they would be preferable solutions.
There is no builtin method which achieves that. However, there is a simple (almost trivial) way to do it:
const sanitized = {};
for (const p in schema)
sanitized[p] = (p in object ? object : schema)[p];
Just retrieve the same key from the other object:
Object.keys(schema).forEach(key => schema[key] = (key in values ? values : schema)[key]);
If you want to create a new object:
var newObj = {};
Object.keys(schema).forEach(key => newObj[key] = (key in values ? values : schema)[key]);
Also, this is compatible with previous version of ES as well (if you do not use arrow function, of course).
The (key in values ? values : schema)[key]) part assures properties that are only in first schema aren't set to undefined
Edited to OP's slightly more complicated request
Just map the value's and schema's keys to individual objects, and spread them. Prioritize values by placing them after:
Object.assign(
schema,
...Object.keys(schema).map(
(key) => ({[key]: schema[key]})
),
...Object.keys(schema).map(
(key) => ({[key]: values[key]})
)
);
If you don't want to overwrite schema, specify a different target for Object.assign():
let sanitized = Object.assign(
{},
...Object.keys(schema).map(
(key) => ({[key]: schema[key]})
),
...Object.keys(schema).map(
(key) => ({[key]: values[key]})
)
);
Following a pattern closer to #Bergi's answer, you could do something less verbose like this with Object.assign():
let sanitized = Object.assign({}, ...Object.keys(schema).map(
(key) => ({[key]: (key in values ? values : schema)[key]})
)
);

Categories

Resources