Conditionally add properties to an object if referencing variable contains string - javascript

Some state
const [submittedSev, setSubmittedSev] = useState('');
const [newSev, setNewSev] = useState('');
Mixpanel events where I'm sending properties that may or may not exist. Sometimes the submittedSev and newSev may be empty strings but this will still send
mixpanel.track('COMPLETED', {
response: props.details.title,
submittedSev,
newSev
});
I'd like to only add submittedSev and newSev properties if the string isn't empty. For sure I could set up a conditional statement and check the string length and send a different Mixpanel event but that doesn't seem succinct enough.
Output of data that sometimes gets sent
{
"response": "hello",
"submittedSev": "",
"newSev": ""
}
How can I only add properties to object if they are not empty strings?

Here's a really concise way to do it using the spread ... operator and evaluating an expression with the AND operator && which returns the second value if the first is truthy:
// Example.js
const name = ''
const email = 'asd#gmail.com'
const phone = ''
const output = {
...(name && {name}),
...(email && {email}),
...(phone && {phone})
}
console.log(output)
So for your code it'd be:
mixpanel.track('COMPLETED', {
response: props.details.title,
...(submittedSev && {submittedSev}),
...(newSev && {newSev})
});

You could centralize the logic that sends the events in a separate function, and go through the properties of the event object and filter out the ones that are not empty strings:
function trackMixpanelEvent(eventType, event) {
const keys = Object.keys(event).filter(
key => Object.prototype.hasOwnProperty.call(event, key));
keys.forEach(key => {
if (event[key] === '') {
delete event[key];
}
});
mixpanel.track(eventType, event);
}
And then just call it without worrying which properties are empty strings:
trackMixpanelEvent('COMPLETED', {
response: props.details.title,
submittedSev,
newSev
});

Related

How can I fix substring to read urls from object correctly?

As the user is typing in a url, I am trying to iterate through an object so that no error message is thrown until a substring doesn't match the beginning of any of the urls in my object.
Object:
export const urlStrings: { [key: string]: string } = {
'www.costco.com': '',
'www.walmart.com': '',
'www.google.com': '',
'www.facebook.com': '',
}
So the user will type and no error will be thrown until a substring is incorrect:
w
ww
www.
www.c
www.ca <--- this should throw error
However, because I have subString set to 1, it only gives me the first letter of the url (item). I want to get everything from the first letter to the increasing index. I have a break in there that stops the for loop.
const correctUrl = (value: string | null): string => {
let errorMessage = ''
let item: string
let subString = 1
if (value && typeof value === 'string') {
// eslint-disable-next-line guard-for-in
for (item in urlStrings) {
if (value?.length <= item?.length && subString <= item?.length) {
if (item.substring(0, subString) === value.substring(0, subString)) {
errorMessage = ''
break
}
}
subString += 1
}
} else {
errorMessage = 'Throw error'
}
return errorMessage
}
Any recs? Also, this is typescript.
It maybe easier to use JS's native array methods to help you. Add the keys to an array, and then use some to check the strings against the value of the input. If the value matches none of them throw the error.
const urlStrings = {
'www.costco.com': '',
'www.walmart.com': '',
'www.google.com': '',
'www.facebook.com': '',
};
const input = document.querySelector('input');
input.addEventListener('input', handleInput);
const validStrings = Object.keys(urlStrings);
function handleInput() {
const { value } = this;
const valid = validStrings.some(str => {
return str.startsWith(value);
});
if (!valid) console.log('URL not valid');
}
<input type="text">
good question!
First off, I'd recommend looking into guard clauses -- your nesting so many layers deep that the logic gets way more complicated to track with.
I think your Throw error is aligned with the wrong if statement -- basically if it succeeds at all, it's never going to throw the error. If you reformat your code with my first suggestion, I think the placement should be pretty clear -- and you shouldn't need a break statement.
Good luck, feel free to ask follow ups!
I think that's a bit more complicated than it needs to be. Array methods will make this pretty trivial. Put the keys of the object (the URLs) into an array, then check if .every one of those URLs does not start with the text typed. If so, then there are no potential matches, and you can return the error string.
const urlStrings = {
'www.costco.com': '',
'www.walmart.com': '',
'www.google.com': '',
'www.facebook.com': '',
};
const urls = Object.keys(urlStrings);
const correctUrl = (value: string | null): string => {
if (value === null) {
return 'Throw error';
}
return urls.every(url => !url.startsWith(value))
? 'Throw error' // None of the URLs matched
: '';
}
The object of urlStrings seems a bit odd though. Unless the values of the object contain data in your actual code, you could consider starting out with a plain array instead.
const urls = [
'www.costco.com',
'www.walmart.com',
'www.google.com',
'www.facebook.com',
];

Can't get key from value within an object

I am trying to get the value associated with a key and create a new object, such as below. However when I assign the value of employeeCountryName to the object newCountryList, i get employeeCountryName returned and not "United Kingdom".
Any thoughts why this may be?
const countryList = {
"United Kingdom": "GBR"
};
const employeeCountryCode = "GBP"
const getKey = (obj, val) => Object.keys(obj).find(key => obj[key] === val);
const employeeCountryName = getKey(countryList, employeeCountryCode);
const newCountryList = {
employeeCountryName: employeeCountryCode
};
Keep in mind that when you define an object, it's keys aren't based on any other variables by default, they are just strings taken as keys. In order to tell javascript to take the value of a variable you have predefined instead of using it directly as a key, you need to add [] around them like this:
const variableName = 'keyToUse';
const object = { [variableName]: 1 } // { keyToUse: 1 }
You can use like that;
const newCountryList = {
[employeeCountryName]: employeeCountryCode
};
By the way, in your case find function couldn't find any key-value pair so it returns undefined. Because "United Kingdom" field has a GBR value and you're trying to find GBP

JS using dot, string notation to map nested and assign errors

Ok, this is an odd one that I just can't seem to get right.
I have a large, complex object to send to a backend. After various attempts, I tried using Joi to validate the schema, and I like it, but passing the errors back to the inputs is a nightmare
The body is subdivided into 5 sections, with each subsection containing between 10-30 fields, some of which are string[], interface[], number[], or general nested interfaces.
I tried writing my own custom validation and the complexity grew outta control.
(I know some of you are thinking "your schema is too complex" and you're right, but its not something I can change right now. Clients blah blah.)
The problem: Joi.validate(myBody) gives me a bunch of errors in the following format:
[ // <- error.details
{
context: {},
message: "X is not allowed to be empty",
path:["path","to","property"], // <- this is the key
type: ""
}
]
How can I map error.details to create a new validation object that I can then use for the form items themselves.
For example:
path = ["path","to","property"] // -> map over to create
let newObj = {
path:{
to: {
property: ""
}
}
}
I hope this make sense.
I want to take an array of vallidation errors, and turn them into a validation object that matches the initial object
The simplest approach IMO would be to use reduce to reverse create the object from the array
["path","to","property"].reduceRight((prev, current) => {
const obj = {};
obj[current] = prev
return obj;
}, "");
This will create the object as described in the original question. You need to use reduceRight rather than reduce so that you create the leaf node first otherwise you have having to try and traverse the graph each time you add a new node and you have to handle setting the leaf node to be a string rather than an object.
Extrapolating out what you are trying to achieve I'm assuming a couple of things:
You want to return a single object rather than an array of objects.
The leaf is likely to be the message.
There will only be a single error message for each path (because it makes life easier).
We can expand upon the above with the deep merge solution from here to create an object to return. The code for that would look like:
const errors = [ // <- error.details
{
context: {},
message: "X is not allowed to be empty",
path:["path","to","property"], // <- this is the key
type: ""
},
{
context: {},
message: "X has to be greater than 0",
path:["path","to","another", "property"], // <- this is the key
type: ""
}
]
function isObject(item) {
return (item && typeof item === 'object' && !Array.isArray(item));
}
function mergeDeep(target, ...sources) {
if (!sources.length) return target;
const source = sources.shift();
if (isObject(target) && isObject(source)) {
for (const key in source) {
if (isObject(source[key])) {
if (!target[key]) Object.assign(target, { [key]: {} });
mergeDeep(target[key], source[key]);
} else {
Object.assign(target, { [key]: source[key] });
}
}
}
return mergeDeep(target, ...sources);
}
errors.map((e) => e.path.reduceRight((prev, current) => {
const obj = {};
obj[current] = prev
return obj;
}, e.message)).reduce((previous, current) => mergeDeep(previous, current), {})
The output from running errors through it would be:
{
path: {
to: {
property: 'X is not allowed to be empty',
another: { property: 'X has to be greater than 0' }
}
}
}
Problem statement being used
I want to take an array of vallidation errors, and turn them into a validation object that matches the initial object
Given: an array of strings (each of which is a prop)
Expected result: an object structured based on the array
Code Snippet
// given an array of props
// starting at root-level & ending at the leaf
const pList = ['root', 'lev1', 'lev2', 'propName'];
// method to transform array into an object
const transformToObj = arr => (
arr
.reverse()
.reduce(
(fin, itm) => ({ [itm]: fin ? {...fin} : '' }),
false
)
);
// test with given array
console.log(transformToObj(pList));
// test with user-input array (comma-separated)
console.log(transformToObj([...(
(prompt('Enter comma-separated path like a,b,c,d'))
.split(',')
.map(x => x.trim())
)]));
Explanation
first reverse the array (so the first item is the inner-most prop)
use .reduce to iterate
at each level, add the item as the outer-prop and the value as the existing object
if this is the inner-most prop, simply add an empty string as value

Destructuring fallback to prevent undefined error?

I have a list of array I do this:
const { id } = myArray.find(
(obj) => obj === true)
If the id is not present it will throw error. How to prevent error in the same time use destructuring? I want to keep the logic in one line.
The issue here is .find() returns undefined once there is no fulfillment for the condition:
The value of the first element in the array that satisfies the provided testing function. Otherwise, undefined is returned.
So probably you can use || operator to check if you have any value returned from .find() or you can replace with empty object {} instead on the right side of the operator.
Probably the option for one-liner is the following:
const myArray = [
{ id: 12 },
{ id: 13 },
{ id: 14 }
];
const { id } = myArray.find(e => e.id === 17) || {};
console.log(id);
So you can destructure id property even if it returned undefined in this way.
Also if you need you can add default value to your destructuring statement based on the documentation which states for Destructuring statement as follows:
A variable can be assigned a default, in the case that the value unpacked from the object is undefined.
const { id = 10 } = {};
console.log(id);
I hope this helps!

How to identify anonymous types in JSON?

I am writing one function on Javascript which needs to address all the anynymous types in a JSON object.
For example,
Typed= {
emails: [{email:'a#a.com'}, {email:'b#a.com'}, {email:'c#a.com'}, {email:'d#a.com'}]
};
is an example of typed array in a JSON because each element inside the array is typed email
while,
Anon= {
emails: ['a#a.com', 'b#a.com', 'c#a.com', 'd#a.com']
};
is a JSON object where emails is collection of some anonymous objects.
Is there any ways that I can differentiate between both in JQuery or Javascript?
The simplest solution is to have the JSON source only return one of the two forms. Then you don't have to branch in your client.
If that's not an option, you could get the values out with JavaScript's handy lazy-evaluation of boolean expressions:
var em = json.emails[0].email || json.emails[0];
That statement will prefer the array-of-objects version, but use the array-of-strings version as a fallback.
(edited in response to clarifying comment below)
You can determine what properties a JS object has at runtime like this:
function enumerate(targetObject){
var props = [];
for (var propName in targetObject ){
props.push(propName);
}
return props;
}
console.log(enumerate({foo:1, bar:'baz'}),join(',')); //"foo, bar"
you could then modulate your logic on the basis of the properties you get back. You'll want to make sure you understand prototypes (specifically what Object.hasOwnProperty does and means), too.
You can use Array iteration methods to quickly check if all (or some) elements of the array have the desired type:
Anon.emails.every(function(e) { return typeof e == "object" }) // false
Typed.emails.every(function(e) { return typeof e == "object" }) // true
or a more generic solution
typeCheck = function(type) {
return function() {
return typeof arguments[0] == type
}
}
Anon.emails.every(typeCheck("object")) // false
Typed.emails.every(typeCheck("object")) // true
(An obligatory warning about iteration methods not being supported in ancient browsers)
How about this:
var istyped = function (a) {
if (typeof(a) !== 'object') {
return false;
}
var count = 0;
for (var key in a) {
count = count + 1;
}
return (count === 1);
}
I'm assuming here you just want to distinguish between regular variables (this would be your anonymous variable) and objects with just one key/value pair inside (this would be your typed variable).
To check if array contains only typed variables you'd just have to loop through it with that function. For example (in newer versions of JavaScript):
Typed.emails.every(istyped) = true
Anon.emails.every(istyped) = false
Why not do a map first:
emails = emails.map(function (email) {
if (typeof email.email === 'string')
return email.email;
});
That will make your emails array an array of just strings. Then you can just process it as usual. There aren't any side-effects if it is an array of strings (email.email will be undefined).
I do stuff like this when I have to make one client deal with multiple versions of an API. Alternatively, you could do the map the other way:
emails = emails.map(function (email) {
if (typeof email === 'string')
return {email: email};
});
This would work better if there could be other information in each object in your emails array.

Categories

Resources