destructure object only if it's truthy - javascript

Suppose I want to destructure my function argument like this
const func = ({field: {subField}}) => subField;
How can I prevent this from throwing an error if field is undefined or null ?

You might use a default value:
const func = ({field: {subField} = {}}) => subField;
It works only with {field: undefined} though, not with null as a value. For that I'd just use
const func = ({field}) => field == null ? null : field.subField;
// or if you don't care about getting both null or undefined respectively
const func = ({field}) => field && field.subField;
See also javascript test for existence of nested object key for general solutions.

You could only part destruction and use for subField a parameter with a check.
var fn = ({ field }, subField = field && field.subField) => subField;
console.log(fn({ field: null }));

A good way to fix both the cases of null and undefined is the following
const func = ({field}) => {
let subField = null;
if(field) {
({subField} = field);
}
return subField
};
If you only want to handle the case when field is undefined, you could just to
const func = ({field: {subField} = {}}) => subField;
whereby if field is undefined the default empty object is used as its value

Related

Null values after mapping Data to an Object

I am trying to map multiple Database entries to an Object, so I can filter it.
async autocomplete(interaction) {
const focusedValue = interaction.options.getFocused();
let choices = await items.findAll({
attributes: ['itemID', 'desc_de'] });
const jsondata = JSON.stringify(choices);
const json = JSON.parse(jsondata);
const itemObj = json.map(function(item) {
return {
itemID: item.itemID,
item_desc: item.desc_de,
};
});
const filtered = itemObj.filter(choice => choice.item_desc.toLowerCase().includes(focusedValue.toLowerCase()));
await interaction.respond(
filtered.map(choice => ({name: choice.item_desc, value: choice.itemID})),
);
The issue I am having is, apparently choice.item_desc is NULL somehow, I am unsure, what I am doing wrong here.
The error is
TypeError: Cannot read properties of null (reading 'toLowerCase')
Before I had an object, which was only holding the itemID and with that it was working fine.
focusedValue may be null to which you cannot apply .toLowerCase(). Try setting a default value to that given that function exists outside the scope of the code provided

How to dynamically ignore certain fields in a javascript object when converting it to json?

Currently, I am using the toJSON() object on a method to ignore any fields that are underscored e.g.
toJSON() {
const properties = Object.getOwnPropertyNames(this);
const publicProperties = properties.filter(property => {
return property.charAt(0) !== '_'
})
const json = publicProperties.reduce((obj, key) => {
obj[key] = this[key]
return obj
}, {})
return json
}
This was fine. But I have the concept of roles in my API and I would like to return private fields if the user is an admin.
This led me to the idea of doing:
toJSON(role='user') {
const properties = Object.getOwnPropertyNames(this);
const publicProperties = properties.filter(property => {
return property.charAt(0) !== '_' || role === 'admin'
})
const json = publicProperties.reduce((obj, key) => {
key = key.charAt(0) === '_' ? key.substring(1) : key
obj[key] = this[key]
return obj
}, {})
return json
}
But then the issue becomes how do I get the role argument passed to the toJSON() method, especially when JSON.stringify() is being called and the method calling JSON.stringify() I might not have access to.
I could set on my object a role property before returning a json response e.g.
const getCurrentProject = async (c) => {
const project = await projectService.getCurrentProject(c.get('projectId'));
project._role = c.get('payload').role
return c.json(project, httpStatus.OK);
};
But that doesn't seem ideal and then there are more issues when JSON.stringify() is called on an array of object as I would have to set that for each object.
My next idea was to use my own json response function that would have a replacer function for JSON.stringify()
const jsonResponse = (context, object, status) => {
const role = c.get('payload').role
const body = JSON.stringify(object, (key, value) => {
// function to set private vars to null based on role
})
headers = 'application/json; charset=UTF-8'
return c.body(body, status, headers)
}
The issue with this is that the replacer function will just set them to null and not hide them and I can't just blindly remove keys with null values as I might need them. I could set them to 'remove' or another placeholder and remove them after but again, it doesn't seem like the best way.
So currently I am confused on what I should do. Is there a way to globally override JSON.stringify() and add the role parameter as an argument, is there a better approach I am missing? Or should I just stick to the _role property and for lists of objects set it for each one.
Thanks!
You can use a replacer function. If you return a Function, Symbol, or undefined, the property is not included in the output. See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#the_replacer_parameter
I would use Object.entries, and then Array.filter the keys you want, finally Object.fromEntries to get back to an object.
example ->
const obj = {
_adminOnly: 'Lets have a party!',
name: 'bob',
age: 22,
_hideme: 'Hide unless admin',
toJSON: function (role='user') {
return Object.fromEntries(
Object.entries(this).
filter(([k]) => {
if (k === 'toJSON') return false;
return role === 'admin'
? true
: k.charAt(0) !== '_'
}
)
);
}
}
console.log(obj.toJSON());
console.log(obj.toJSON('admin'));

Can't implement simple optional chaining

I am trying to implement a simple optional chaining state update. What I want is for the items to ONLY be changed IF the item is defined/exists; if not/is undefined, I want the item to keep the previous state (e.g userID should remain = 2 if not updated).
To test this out I created an object with three variables:
const userObj = {
firstName: "",
lastName: "",
userID: 2,
};
Then I created a function to update state:
const updateState = (item) => {
return {
userObj.firstName = item?.firstName,
userObj.lastName = item?.lastName,
userObj.userID = item?.userID,
};
};
Finally I pass the item which contains only one item to update (firstName) and I call the function:
const item = {
firstName: "None",
};
console.log(updateState(item));
The output:
userObj.firstName = item?.firstName,
^
SyntaxError: Unexpected token '.'
But when I hover over userObj I can see its properties:
You get this error because what you are trying to return is an object with dots in its keys.
You're mixing assignment and object creation. In addition item?.firstName only helps with the case that item is null or undefined in a sense that it fails fast instead of throwing an exception because you can't access null.firstName.
For this kind of default you'd have to do something along the lines of typeof item.firstName === 'string' ? item.firstName : userObj.firstName.
To pull everything together:
const func = (value, fallback) => (
typeof value === typeof fallback
? value
: fallback
);
const updateState = (item) => {
userObj.firstName = func(item?.firstName, item.firstName);
userObj.lastName = func(item?.lastName, item.lastName);
userObj.userID = func(item?.userID, item.userId);
return userObj;
};
Note however that this function will mutate userObj.
Edit: default was a poor naming choice.
You can do it this way, using javascript object destructuring
const updateState = (item) => {
return {
...userObj,
...item,
};
};
so it will only update the key and value that was passed, i hope the question is not on typescript
You can use null coalescing in conjunction with optional chaining:
const updateState = item => ({
userObj.firstName = item?.firstName ?? userObj.firstName ,
userObj.lastName = item?.lastName ?? userObj.lastName ,
userObj.userID = item?.userID ?? userObj.userId ,
});
You could use the spread operator:
const updateState = item => {
userObj = { ...userObj, ...item };
return userObj;
}
Or you can use lodash's defaults() function:
const _ = require('lodash');
const updateState = item => {
userObj = _.defaults(userObj, item)
return userObj;
}
Or... if you really want to mutate the state object, rather than creating a new one and replacing it, roll your own, similar:
const updateState = item => {
for (const key in item ) {
const hasValue = key != null && key != undefined && key != NaN ;
if ( hasValue ) {
userObj[prop] = item[prop];
}
}
}
There is, as they say, more than one way to skin a cat.
[Edited: Add explanation of the spread operator]
The spread operator,
const obj = { ...obj1, ...obj2, . . . , ...objN };
is somewhat akin to calling a function like this:
const obj = mergeObjects( obj1, obj2, . . ., objN );
where mergeObjects() is defined as:
function mergeObjects(...objects) {
const mergedObject = {};
for (const obj of objects ) {
for (const key in obj ) {
mergedObject[key] = item[key];
}
}
return mergedObject;
}
Or perhaps a better explanation might be done using Object.assign(). One could say that an expression like:
const obj = {
prop1: 'a' ,
prop2: 'b' ,
...obj1 ,
prop3: 'c' ,
prop4: 'd' ,
...obj2 ,
prop5: 'e' ,
prop6: 'f' ,
...obj3 ,
}
is the equivalent of this:
const obj = Object.assign( {},
{
prop1: 'a' ,
prop2: 'b' ,
},
obj1 ,
{
prop3: 'c' ,
prop4: 'd' ,
} ,
obj2 ,
{
prop5: 'e' ,
prop6: 'f' ,
} ,
obj3 ,
);

Conditionally rendering content in react

I am having a functional component that takes two items as props. The values of each properties could be undefined, "", null, "null", or a valid string (for example, "test").
I need to conditionally render these props based on values
if prop1 and prop2 are both present then it should display as prop1(prop2), if either one of them are present then it should either be prop1 or just prop2. In case both of them are not present then should display "Not Available". Only valid strings should be taken. If values has either undefined, "", null, "null" it should not be displayed.
I am having trouble building up the logic. This is what I have tried.
const Test = (props) => {
let { prop1, prop2 } = props;
let content: string;
if ((!prop1 || prop1 === "null") && (!prop2 || prop2 === "null")) {
content = "Not Available";
} else if (!!prop1 && !!prop2) {
content = `${prop1} (${prop2})`;
} else {
content = `${prop1 || prop2}`;
}
return (
<>
{content}
</>
);
}
This maybe one way to achieve the desired objective:
const isPropInvalid = inp => [undefined, null, '', ' '].includes(inp);
const content = isPropInvalid(prop1)
? isPropInvalid(prop2) ? 'Not Available' : prop2
: isPropInvalid(prop2) ? prop1 : `${prop1} (${prop2})`
Explanation
set-up an array with elements that are considered 'invalid' (ie, undefined, null, '', 'null')
use simple if-else or ?: ternary-operators to assign the appropriate value to content based on whether one or both props are invalid.
Code Snippet
const isPropInvalid = inp => [undefined, null, '', 'null'].includes(inp);
const content = (prop1, prop2) => isPropInvalid(prop1)
? isPropInvalid(prop2) ? 'Not Available' : prop2
: isPropInvalid(prop2) ? prop1 : `${prop1} (${prop2})`;
console.log(content('abc', 'xyz'));
console.log(content());
console.log(content(null, 'xyz'));
console.log(content('abc', 'null'));
I guess this is a tricky condition by itself, so I wouldn't worry too much if it looks a bit weird.
One think you can do tho is to organize the component code in variables and smaller functions like so:
const isDefined = (value) => value !== "null" && !!value
const buildString = (prop1, prop2) => {
let string = prop1 || prop2
return prop1 && prop2
? `${string} (${prop2})`
: string
}
const Test = ({ prop1, prop2 }) => {
const someDefined = isDefined(prop1) || isDefined(prop2);
return (
<>
{!someAreDefined && "Not Available"}
{someAreDefined && buildString(prop1, prop2)}
</>
);
}
I think that helps a lot with readability and understanding the flow and possible outputs for this component.
How about a separate function that returns whether or not the value is valid?
function propIsValid(value) {
// valid if truthy and not "null" (string)
return !!prop && value != "null";
}
The above checks for a thuthy value, which means that all falsy values are considered invalid. This includes null, undefined and "", but also false, NaN and 0. Which might or might not be a problem, depending on your context.
If you want a more target approach you could use the following:
const invalid = [undefined, null, "", "null"];
return !invalid.includes(value);
Then simplify your component to:
const Test = ({ prop1, prop2 }) => {
const allValid = [prop1, prop2].every(propIsValid);
const firstValid = [prop1, prop2].find(propIsValid);
if (allValid) {
return <>{prop1}({prop2})</>;
} else if (firstValid) {
return <>{firstValid}</>;
} else {
return <>Not Available</>;
}
}
This uses every() to check if both are valid, but this could also be written as propIsValid(prop1) && propIsValid(prop2). find() is used to find the first valid value (if any), this does assume that a valid value is always thuthy.
Just use a ternary
(prop1 && !/null/.test(prop1)) && (prop2 && !/null/.test(prop2)) ? `${prop1}(${prop2})` : prop1 && !/null/.test(prop1) ? `${prop1}` : prop2 && !/null/.test(prop2) ? `${prop2}` : 'Not Available';
Since undefined "" and null are falsey in nature it will only use regex to test for the pattern null if it needs to. If there are any other values that you want to be considered invalid, just add them to the regex.

Check if an object is empty and with undefined properties

I'm trying to check the below,
USECASE: 1
var obj = {};
_.isEmpty(obj); ====> returns true
USECASE: 2
var obj = { model: undefined, vendor: undefined, type: undefined }
_.isEmpty(obj); ====> returns false
In usecase 2 is there any other function that i could leverage to return true since all the properties are undefined.
I referred this http://www.ericfeminella.com/blog/2012/08/18/determining-if-an-object-is-empty-with-underscore-lo-dash/ but not sure how to implement it without modifying the native method.
Without Underscore:
const objEmpty = obj => Object.keys(obj).every(key => obj[key] === undefined);
Will accept an object and return true if the object is empty (no properties) or if all the properties have undefined value. Else returns false.
You could use every and pass in the isUndefined predicate:
var obj = { model: undefined, vendor: undefined, type: undefined }
var result = _.every(obj, _.isUndefined);
Alternatively you could omit the undefined values and check whether the object is empty:
var result = _.isEmpty(_.omit(obj, _.isUndefined));

Categories