Function parameter value isn't working in object-destructuring in JS - javascript

Let's assume that I've the following object:
let filters = {
brands: { ... },
price: { ... },
sizes: { ... },
...
}
The properties of the filters object will be set by the users. Which means sometimes the filters object may contain just brands, sometimes it may contain brands & price and so on.
I've written the following function to extract a specific property from the filters object:
let extractProperty = (propertyName) => {
({ propertyName, ...rest } = filters); // <-- propertyName isn't working here
console.log(propertyName);
}
extractProperty("brands");
If I invoke the above function, the console displays undefined.
Can anyone please point me out what I'm missing here?
CodePen Link
Note:
I've already resolved this issue using lodash.omit method. But I'm still curious to know why function parameter value isn't working in object-destructuring.
Not Duplicate:
This question is about passing default value

That code is looking for a property called propertyName, literally. To use the value in propertyName as the property name, you need to use computed notation, and you'll need to specify where to put the property's value. For instance, to put it in an existing example variable:
let extractProperty = (propertyName) => {
({ [propertyName]: example, ...rest } = filters);
// ^−−−−−−−−−−−−^^^^^^^^^^
console.log(example);
};
extractProperty("brands");
Your code is written assuming that rest already exists, but I suspect you really want to declare it locally, along with the variable to receive the property value:
let extractProperty = (propertyName) => {
const { [propertyName]: example, ...rest } = filters;
console.log(example);
};
extractProperty("brands");
Without const, let (or var, but var is deprecated), unless you have rest declared in an enclosing scope, that code will either fail with a ReferenceError (in strict mode) or fall prey to what I call The Horror of Implicit Globals (in loose mode), automatically creating a global variable.

why use destructuring here when you just want to get a property?
let filters = {
brands: { value:'b' },
price: { value:'p' },
sizes: { value:'s' },
}
let extractProperty = propertyName =>
console.log(filters[propertyName])
extractProperty("brands");

Related

Is it possible to provide default parameters with rest operator

I was learning from an ES6 essential course and trying default parameters and rest operator for functions.
I have defined a function sayHi as below with default parameters and then rest operator which does not gives the desired output.
const sayHi = (greetings, ...names) => {
names.forEach(item => {
console.log(`${greetings} ${item}`);
});
}
sayHi('Hi', 'Ahsan', 'Awais', 'Haseeb');
The above snippet works as desired. but when I tried to set a default parameter value for greetings variable it works but gives unwanted result i.e. value 'Ahsan' is taken by the greetings variable.
const sayHi = (greetings = ' Hi', ...names) => {
names.forEach(item => {
console.log(`${greetings} ${item}`);
});
}
sayHi('Ahsan', 'Awais', 'Haseeb');
Is there a way I can set default parameters in function like above before rest operator?
You can't, no. The rest parameter only picks up the "rest" of the parameters not consumed by any previous formal parameters, so greeting will always receive the first argument's value.
Separately, since both the names and the greeting have the same type, you can't provide a default at all if you want to accept the names that way.
A couple of options for you:
A curried function
You could have a function that accepts the greeting and returns a function that uses it with whatever you pass it:
const greet = (greeting = "Hi") => (...names) => {
for (const name of names) {
console.log(`${greeting} ${name}`);
}
};
greet()("Ahsan", "Awais", "Haseeb");
greet("Hello")("Ahsan", "Awais", "Haseeb");
Note how we called that:
greet()("Ahsan", "Awais", "Haseeb");
greet() creates the function using the default greeting. Then we call that function by using ("Ahsan", "Awais", "Haseeb") on it. greet("Hello") creates a function that uses the greeting "Hello" (and then we call it).
(I also took the liberty of using for..of rather than forEach, but it's a matter of style.)
Take names as an array
Another option is to accept the names as an array. That way, we can tell inside the function whether we got a greeting or not:
const greet = (greeting, names) => {
if (Array.isArray(greeting)) {
names = greeting;
greeting = "Hi";
}
for (const name of names) {
console.log(`${greeting} ${name}`);
}
};
greet(["Ahsan", "Awais", "Haseeb"]);
greet("Hello", ["Ahsan", "Awais", "Haseeb"]);
you just have to pass undefined in the 1st parameter to skip optional arg
think of it like this greetings = typeof greetings != 'undefined' ? greetings : "hi" which means that check the value of greetings and if it's undefined (not provided) use the default value
edit: here is the code snippet
const sayHi = (greetings = ' Hi', ...names) => {
names.forEach(item => {
console.log(`${greetings} ${item}`);
});
}
sayHi(undefined, 'Ahsan', 'Awais', 'Haseeb');

How to destructure an object that is stored as a state value

Within a React App component, I am calling an API and storing the response within a local state. I then want to destructure the object that is stored in that state, but I can't just destructure right below the useEffect because it will throw an error before the call is completed.
Also, I don't want to break up the object within the useEffect, because I want the entire response for other things.
Here is an example:
const MyComponent = () => {
const [calledObj, setCalledObj] = useState({})
useEffect(() => {
//Calling API here and setting object response as calledObj State
setCalledObj(apiResponse)
}, []);
//This will throw an error when the API response has not been sent back yet.//
// While this would be easy to write the whole path in the return, the actual path is really long.//
const { name } = calledObj.person
return (<div>{name}</div>)
}
Where can I destructure or how can I work around this?
You can use optional chaining and/or the nullish coelescing operator to work around it.
Note: IE doesn't support either of these, but babel will polyfill them.
const { name } = calledObj?.person ?? {};
The optional chaining (the ?. in calledObj?.person) prevents it from blowing up if calledObj is undefined.
The nullish coalescing operator (??) returns {} if calledObj.person isn't there.
With this combination the right side is guaranteed to evaluate to an object, so the destructuring on the left never blows up.
let calledObject; // undefined;
// name is undefined, but it doesn't blow up.
const { name: name1 } = calledObject?.person ?? {};
console.log(`name 1: ${name1}`); // name 1: undefined
// ----------------
// now it's an object, but doesn't have a person property
calledObject = {};
// name is still undefined, still doesn't blow up.
const { name: name2 } = calledObject?.person ?? {};
console.log(`name 2: ${name2}`); // name 1: undefined
// ----------------
// with person.name present…
calledObject.person = { name: 'joe' };
const { name: name3 } = calledObject?.person ?? {};
// …it works as you'd expect
console.log(`name 3: ${name3}`); // name 3: 'joe'
Depending on what you want your name variable to default to on first render, I guess you could do something like this:
const { name } = calledObj.person ? calledObj.person : {name: ''}
You can initialize your state with:
const [calledObj, setCalledObj] = useState({person: {}})
And this will put undefined in 'name' but is not breaking your code.

flow error accessing optional prop after truthy check

flow 0.67.1 (but behavior continues to exist in 0.73.1)
Example:
type PropOptional = {
prop?: ComplexType
};
type ComplexType = {
callable: () => void,
anotherCallable: () => void
};
function usePropOptional(arg1: PropOptional) {
if (arg1.prop) {
arg1.prop.callable();
arg1.prop.anotherCallable();
arg1.prop.callable();
}
};
The function checks for the presence of arg1.prop before accessing any properties on arg1.prop. This should be sufficient to verify that arg1.prop is defined.
Flow is fine with the first time an arg1.prop property is accessed, which is the call to arg1.prop.callable() on the first line inside the if block. However, flow generates errors on subsequent attempts to access arg1.prop properties in the exact same if block:
arg1.prop.anotherCallable();
arg1.prop.callable();
I am forced to either prepend each line with a rote arg1.prop && truthy check, or reassign arg1.prop to a local variable inside the if block:
function usePropOptional(arg1: PropOptional) {
if (arg1.prop) {
const reallyExists = arg1.prop;
reallyExists.callable();
reallyExists.anotherCallable();
reallyExists.callable();
}
};
This doesn't feel right. What am I doing wrong or missing?
You can check this in a flow repl here on flow.org.
This is documented in FlowType's Type Refinement section:
Refinement Invalidations
It is also possible to invalidate refinements, for example:
// #flow
function otherMethod() { /* ... */ }
function method(value: { prop?: string }) {
if (value.prop) {
otherMethod();
// $ExpectError
value.prop.charAt(0);
}
}
The reason for this is that we don’t know that otherMethod() hasn’t
done something to our value.
...
There’s a straightforward way to get around this. Store the value
before calling another method and use the stored value instead. This
way you can prevent the refinement from invalidating.
// #flow
function otherMethod() { /* ... */ }
function method(value: { prop?: string }) {
if (value.prop) {
var prop = value.prop;
otherMethod();
prop.charAt(0);
}
}
So the workaround in your final case appears to be the suggested way to avoid this problem.

Pass Current Object Key

Is there any way to pass the current key of an object as a parameter to a method executed as its value? For example:
VersionOne: {
welcomeMessage: this.localization.get(this.currentKey(?))
},
VersionTwo: {
welcomeMessage: this.localization.get(this.currentKey(?))
}
I know that I can just write out the keys manually, but for long keys, I don't want to duplicate them.
You can't do it before the object has been defined, but you can keep your code DRY by assigning it later:
const versions = {};
['VersionOne', 'VersionTwo'].forEach((version) => {
versions[version] = {
welcomeMessage: () => console.log(version),
};
});
versions.VersionTwo.welcomeMessage();

Assigning a declared variable to a string in array javascript

Let's say I have these global variables:
var name, car;
Then I declare some values:
const languageStrings = {
WELCOME_MESSAGE: 'Welcome #name, you have a #car',
NAME_USER: "#name",
CAR_USER: '#car'
};
And then I need to assign it to a function.
For example:
if (firstTime){
welcomeMsg = WELCOME_MESSAGE;
}
Now, I have two questions:
1) How would I insert a variable inside of a string so it is dynamically updated when the value pair is called?
2) How would I do the same using JSON?
You can't have a JSON structure or string "automatically" update when some variable changes. There are other ways to do this type of templating, though. You could create a function to create a welcome message when you need one:
function getWelcomeMessage(name, car) {
return "Welcome "+name+", you have a "+car;
}
Then you'd do something like welcomeMsg = getWelcomeMessage("Joe", "Camry");
If you don't want to write a function for every template (i.e. if you have lots of them), then you could use String.replace like this:
function applyTemplate(template, params) {
return template.replace(/#(\w+)/g, (m, name) => params[name]);
}
Example usage:
function applyTemplate(template, params) {
return template.replace(/#(\w+)/g, (m, name) => params[name]);
}
const WELCOME_TEMPLATE = "Welcome #name, you have a #car";
var name = "Joe";
var car = "Camry";
var welcomeMessage = applyTemplate(WELCOME_TEMPLATE, {name, car});
console.log(welcomeMessage);
You would have to make a function that returns the value of the variable.
In your case:
welcomeMessage = function(){
return WELCOME_MESSAGE
}
and you would reference the variable with:
welcomeMessage()
So, you'd be assigning a variable as a function that returns the current value of the other variable. You get the value by calling your variable as a function.
String in JavaScript is primitive type, it's passed by value. So once a variable is assigned with a string, it will never change until you explicitly assign another value (or object reference) to it.
However, you can ask object type for help, which could make your data reactively (or dynamically, if you prefer this word) update under certain conditions.
var languageStrings = {
WELCOME_MESSAGE: '',
NAME_USER: '',
CAR_USER: ''
}
Object.defineProperty(languageStrings, 'name', {
get: function (name) {
return this.NAME_USER
},
set: function (name) {
this.NAME_USER = name
this.WELCOME_MESSAGE = `Welcome ${this.name}, you have a ${this.car}.`
}
})
Object.defineProperty(languageStrings, 'car', {
get: function (car) {
return this.CAR_USER
},
set: function (car) {
this.CAR_USER = car
this.WELCOME_MESSAGE = `Welcome ${this.name}, you have a ${this.car}.`
}
})
Now, whenever you change languageStrings.name or languageStrings.car, all three strings you want will automatically adopt the new value you just set:
languageStrings.name = 'Leo'
languageStrings.car = 'BMW'
for (let key in languageStrings) {
console.log(`${key}: ${languageStrings[key]}`)
}
// WELCOME_MESSAGE: Welcome Leo, you have a BMW.
// NAME_USER: Leo
// CAR_USER: BMW
You don't have to manually call applyTemplate all the time, like in #qxz's answer (I'm not saying his wrong, though).
Also, please notice that even name and car are not enumerable - they will not be accessed with for in, for of, or Object.keys! This is great, your implementation details are concealed, no worries or confusions to other developers who use your code.
In fact, such reactive model is widely used in front-end MV* frameworks nowadays, e.g. Vue.
Regarding your second question, I didn't get it honestly. Just JSON.parse then it's all ordinary JavaScript, isn't it?
the answer to your question on how to insert variables inside a string is:
WELCOME_MESSAGE: 'Welcome ' + name + ', you have a ' + car,
or before defining:
function mesg(name, car){
return "Welcome" + name + ", you have a " + car;
}
mesg(bob, BMW);
in this case, the name and car is defined after.

Categories