Passing an object as a parameter of a function - javascript

i am having an hard time understanding the usability of passing an object as a parameter of a function.
I am trying to learn to use Next.js and they usually have this way of interacting with their code.
Can someone explain me in short words the usability of it ? I have an example listed below.
Thank you !
function Page({ data }) {
// Render data...
}
Understanding the usability of passing an object as an argument of a function.
Explained me with an example the usability of it.

This syntax is called Destructuring assignment.
So { data } in function Page({ data }){ ... is not an object definition, but a destructuring of the object passed as the first argument of the function,
which results in a variable data available in the function body.
More details ...
Preface
I assume you are familiar with basic destructuring assignments, which look like e.g.:
const person = { name: 'Alice', age: 20 }; // <-- define object
const { name, age } = person; // <-- destructure properties from the object
But note that you not necessarily need to bind this object to a variable name to be able to destructure it:
const { name, age } = { name: 'Alice', age: 20 }; // <-- define object and destructure it right away, without a variable name
Destructuring of arguments in React components
In a React function-component you can pass properties and access them inside the component like this:
function Page( props ) {
return <div>
Data: { props.data }
</div>;
};
function App() {
return <Page data="Hello" />;
};
You can also destructure the props at the beginning of the function, so that you don't have to type props.... for each property:
function Page( props ) {
const { data } = props;
return <div>
Data: { data }
</div>;
};
React components always have the same arguments, so you basically always have the props argument. Here "props" is a variable name that is bound to the props object (which contains all the properties passed to the component).
But you almost never actually need the props object itself, you likely only want the properties inside it.
So you don't really need to bind the object with the properties to the name "props", only to destructure it right away in the next line,
which makes the props object obsolete for the rest of the function body.
For this, javascript has a "shortcut syntax" to destructure an argument directly in the function arguments section, without specifying a name for the argument:
function Page({ data }){
return <div>
Data: { data }
</div>;
};
This is common syntax. I wouldn't say that it is better or worse than other options.

The answer was great and one huge positive benefit of an object prop is you dont have to pass parameters in a specific order, which is super helpful when function argument list becomes long.

This is called destructing. Works for objects and arrays:
const obj = { x: 'hello', y: 'world', z: 'whats up' };
const { x } = obj;
console.log('obj.x is', obj.x);
console.log('x is', x);
const { y, z: myNewName } = obj;
console.log('obj.y is', obj.y);
console.log('y is', y);
console.log('obj.z is', obj.z);
console.log('myNewName is', myNewName);
// console.log(z) will error
const arr = ['value', v => console.log(v)];
const [whateverIWantToCallIt, namesDoNotMatterJustTheIndex] = arr;
console.log('accessing from deconstruction of arr')
console.log(whateverIWantToCallIt);
console.log(namesDoNotMatterJustTheIndex('whats up'));
console.log('accessing from arr')
console.log(arr[0]);
console.log(arr[1]('whats up'));

Related

Pinia 'return {myStore}' vs 'return myStore'

I want to use a Pinia store in a component of my Vue app and I can't figure out why the store has to be returned in { }? What is the difference between return {foo} vs return foo?
import { usePiniaStore } from "../stores/mainStore";
export default {
setup() {
const piniaStore = usePiniaStore();
return { piniaStore }; // why isn't it 'return piniaStore' ?
},
};
This is really not about Pinia but about what Vue expects as a return value from setup() function. It expects an object. If you try to return something else, Vue gives you an error.
// this will give you an error "setup() should return an object. Received: number"
<script>
import { defineComponent } from 'vue'
export default defineComponent({
setup() {
let myVariable = 10
return myVariable
}
})
</script>
Reason for this is that Vue needs to iterate the properties of returned object (so it knows both the value and it's name) and create properties with same names on component instance (so they are accessible in template). This is important.
The code from your example:
return { piniaStore }
is actually same as:
// creating new JS object
const returnObject = {
// first is property name
// second is property value (from existing variable)
piniaStore: piniaStore
}
return returnObject
...and it is a valid code from Vue's point of view
Important thing to remember is that only properties of the returned object are accessible from the template
// you can do this BUT only inner properties of the "myObject" will be accessible in the template
<script>
import { defineComponent } from 'vue'
export default defineComponent({
setup() {
let myObject = {
variableA: 10,
variableB: "some string"
}
return myObject
}
})
</script>
Using <div v-if="variableA"> will work. Using <div v-if="myObject"> will not.
Pinia stores are actually objects so returning them directly from setup (without wrapping them in another object) is probably legal and will work. But all above still applies. Your template has no access to piniaStore only to properties (state or getters) and functions (actions) defined on that piniaStore store
This is called Object Destructring. If a module is returning multiple objects ie {foo, goo, loo} and you want to pick just one foo. you can use return {foo}.
But if the module is returning only one object foo, you can use return foo.
https://www.javascripttutorial.net/es6/javascript-object-destructuring/

How to access parameter names (or get args as an object) in method decorator in typescript?

I am trying to access parameter names in method decorator.
function log(filter: string[] = []) {
return function(
target: any,
propertyKey: string,
descriptor: PropertyDescriptor,
) {
const originalFunc = descriptor.value;
descriptor.value = async function(...args: any[]) {
this.logger.log(
`Start ${propertyKey} function`,
`${this.constructor.name}.${propertyKey}`,
);
// filter is parameter names: to not log them
this.logger.verbose(
`${propertyKey} function Input Parameters is ${args}`,
);
const result = await originalFunc.apply(this, args);
this.logger.verbose(`${propertyKey} function returned: ${result}`);
this.logger.log(`End ${propertyKey} function`);
return result;
};
return descriptor;
};
}
What I am trying to do is to write a logger decorator. this decorator would log method name with its args, and its result after the method has been ran.
This is no problem, but I also want to filter on what param gets logged.
We don't know what params we would get, or how many cause this decorator would be used on variety of methods.
One way I could think of is to get the param names. for example: if the method has three params arbitraryMethod(name: string, age: number, married: boolean) , I would like to get this as something like:
argNames = ['name', 'age', 'married'];
argValues = ['Jack', 23, false];
Or get it in this way:
const args {
name: 'Jack',
age: 23,
married: false
};
There is some other way to filter, which is to get indexes instead of param names, and filter by it, but it wouldn't be elegant and nice and probably a little hard to use. So I avoid using this approach.
I am open to any other solution, if you have it.
Thanks in advance.
You can access to the array of parameters when you are inside the decorator once you want to use the original function, as all of them are inside the args[] parameter
descriptor.value = function (...args: any[]) {
// now you can access to args[0, 1...]
// Call the original function if needed
return originalValue.apply(this, args);
};
Hope this helps,

What properties will rest skip in Object Destructuring?

I am using rest parameter to do some operations, but got some properties disappeared in result.
The data format of this.props looks like this:
```
this.props = {
data: [{...}, ...],
...
}
```
And I tried to rest it in this way:
```
let {showWaveAnimation, ...otherProps} = this.props;
console.log('data' in otherProps); // false
console.log('data' in this.props); //true
```
Why would 'data' property lost after I tried rest operation?
According to MDN, I found the description:
rest parameters are only the ones that haven't been given a separate name (i.e. formally defined in function expression), while the arguments object contains all arguments passed to the function;
What does separate name here means? Is it means properties that extends from its prototype would not be rest? But after I tried the following statements, I got the same result, I am confusing.
```
class Parent {
constructor() {
this.props = {
data: '123',
};
}
}
class Child extends Parent {
constructor() {
super();
this.props.childProp = 'test';
let {test, ...otherProps} = this.props;
console.log('data' in this.props, 'data' in otherProps);
// true true
}
}
new Child();
```
The former code behaviors abnormal after Babel's transpiling, could it be Babel's plugin problem?
ADD: I found this concept may be more precise in describing my rest operations. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Rest_in_Object_Destructuring
I figured out why 'data' disappeared in my rest result, it's the enumerable property of 'data' field!
Once, the property is set as enumerable: false, we can't get it with the operation of rest in object destructuring.
According to MDN
The Rest/Spread Properties for ECMAScript proposal (stage 3) adds the rest syntax to destructuring. Rest properties collect the remaining own enumerable property keys that are not already picked off by the destructuring pattern.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Rest_in_Object_Destructuring
I think this example can explain my ideas more precisely:
const person = {
name: 'Dave',
surname: 'Bowman'
};
Object.defineProperty(person, 'age', {
enumerable: false, // Make the property non-enumerable
value: 25
});
console.log(person['age']); // => 25
const clone = {
...person
};
console.log(clone); // => { name: 'Dave', surname: 'Bowman' }
The rest parameter allows you to get all the remaining attributes not already selected by identifier preceding it in the assignment.
Let's take a simple example:
const obj = {
a: 1,
b: 2,
c: 3
}
const { a, ...rest } = obj;
console.log(a);
console.log(rest);
Above obj has a property named a so it is destructured from obj and assigned to the variable a, then all other properties are assigned in an object to rest.
However, what you have is more like:
const obj = {
a: 1,
b: 2,
c: 3
}
const { d, ...rest } = obj;
console.log(d);
console.log(rest);
Here obj doesn't have a property named d. So it is destructured from obj as undefined and assgined to the variable a. Then all other properties not already assigned, i.e. a, b and c are given to rest.
In your code otherProps has the same properties as this.props, because object this.props doesn't have a property named test. In other words, in order to destructure an object you have to know its structure.
rest parameters are only the ones that haven't been given a separate name (i.e. formally defined in function expression), while the arguments object contains all arguments passed to the function;
What does separate name here means? Is it means properties that extends from its prototype would not be rest? But after I tried the following statements, I got the same result, I am confusing.
Given a function definition function foo(a, b, ...c) {}, arguments a, and b are the named parameters, whereas the rest of the passed arguments are spread into the last variable c. Now, c is a named variable, but any parameters passed from the third on will just be accessible in c, i.e. c[0].
Now, your console.log error is stemming from your use of the in operator. You defined an object const foo = {'data': 'bar'}. 'data' in foo is true, but 'blarg' in foo is false since foo doesn't contain a key/property named 'blarg'.
You're actually using the spread operator to destructure your props object into several named variables.
const object = {
data: 'foo',
other: 'bar',
moreData: [1,2,3],
};
// Example usage of the spread operator
const { data, ...allTheRest} = object; // data is destructured, other and moreData into allTheRest
console.log(data);
console.log(allTheRest);
// in operator
console.log('data' in object); // true
console.log('data' in allTheRest); // false
console.log('moreData' in allTheRest); // true
// This is an example of the rest operator
const foo = (...allTheArgs) => {
allTheArgs.forEach(e => console.log(e));
}
foo(1,2,3); // all the args are collected into a single array variable in the function
const bar = (a, b, ...c) => {
console.log(`a: ${a}`);
console.log(`b: ${b}`);
console.log(`c: ${c}`);
};
bar('this is first param', 42, 'foo', 'bar', '1', 2, 'stackoverflow');

Methods on an object created via composition can't access all properties

I'm building the concept of a family for a product, with members being of different types (accountHolder, payingCustomer, student, and so on). Originally I built these as sub-classes of FamilyMember, but I ended up with some repeated code and eventually bumped into a significant problem: a student of our platform can also be the sole payingCustomer and accountHolder.
Given how object composition is widely touted as a good idea in JS, I decided to go that route. However, the methods of a particular object type (e.g. accountHolder) can't access properties of the instantiated object, if the property belong to another object type (e.g. student).
To make this more objective I've decided to replicate the behaviour using the following code:
const person = (props) => {
let state = {
name: props.name,
}
state.isOfAge = () => {
// state.isAdult is always undefined because
// isAdult doesn't exist in this object
return state.isAdult === true
}
return state
}
const adult = (props) => {
return {
isAdult: true,
}
}
const factory = (props) => {
return Object.assign({}, person(props), adult(props))
}
const john = factory({
name: 'John',
})
console.clear()
console.log(john) // { isAdult: true, name: "John", isOfAge... }
console.log(john.isOfAge()) // false
I was expecting john's method isOfAge to be able to access the property isAdult, since it's in the object. However, conceptually I understand why it doesn't work: isOfAge is a method of state, not the resulting adult instance.
If I were using classes or even a traditional prototype/constructor mechanism I knew how to make it work (e.g. attaching to prototype). With object composition I've no idea how to get there, probably due to lacking experience with FP.
Thanks for the help!
You can use this instead of state inside isOfAge. That way, the this will be deduces when the method isOfAge gets called, it will be bound to whatever object it is called on. Though, you'll have to use a regular function instead of an arrow one for that to work (arrow functions don't have a this):
const person = (props) => {
let state = {
name: props.name,
}
state.isOfAge = function() { // use a regular function
return this.isAdult === true // use this here instead of state
}
return state
}
const adult = (props) => {
return {
isAdult: true,
}
}
const factory = (props) => {
return Object.assign({}, person(props), adult(props))
}
const john = factory({
name: 'John',
})
console.log(john);
console.log(john.isOfAge()); // returns 'true' because 'this' inside 'isOfAge' will be 'john'
Object Composition
All objects made from other objects and language primitives are composite objects.
The act of creating a composite object is known as composition.
...
Concatenation composes objects by extending an existing object with new properties, e.g., Object.assign(destination, a, b), {...a, ...b}.
...                      The Hidden Treasures of Object Composition
So from your pattern and use of a factory function it looks like concatenation? The demo below is a concatenation composition. Note the parenthesis wrapped around the brackets of payment:
const payment = (status) => ({...})
this allows payment to be returned as an object instead of a function. If you have data that's a little more flexible, you'll need less methods. name: string and age: number are the properties I used considering it practical or in your case name: string and adult: boolean.
Demo
const payment = (status) => ({
adult: () => status.age > 17 ? true : false,
account: () => status.adult() ? 'holder' : 'student'
});
const member = (name, age) => {
let status = {
name,
age
};
return Object.assign(status, payment(status));
};
const soze = member('Kaiser Soze', 57);
console.log(soze);
console.log(soze.adult());
console.log(soze.account());
const jr = member('Kaiser Soze Jr.', 13);
console.log(jr);
console.log(jr.adult());
console.log(jr.account());

Strange error when serializing to JSON an array of objects which have a toJSON method

I use a NSwag to generate TypeScript typings and classes for swagger API endpoints. The resulting classes contain a .toJSON() method for each object which gets called when serializing objects to JSON using JSON.stringify().
All works fine when serializing a single object, but when I try to serialize an Array of objects it throws a weird error:
angular.js:14199 TypeError: Cannot create property 'code' on string '0'
at Dashboard.toJSON (App/models/api.js:785:34)
at JSON.stringify (<anonymous>)
and the code that triggers it is pretty simple:
console.log(JSON.stringify([
Dashboard.fromJS({
code: "1212312",
name: "tresads",
description: "some description"
}),
Dashboard.fromJS({
code: "1212312",
name: "tresads",
description: "some description"
})
]));
An excerpt of the class:
export class Dashboard implements IDashboard {
code?: string | undefined;
...
constructor(data?: IDashboard) {
if (data) {
for (var property in data) {
if (data.hasOwnProperty(property))
(<any>this)[property] = (<any>data)[property];
}
}
}
init(data?: any) {
if (data) {
this.code = data["code"];
...
}
}
static fromJS(data: any): Dashboard {
let result = new Dashboard();
result.init(data);
return result;
}
toJSON(data?: any) {
data = data ? data : {};
data["code"] = this.code;
...
return data;
}
clone() {
const json = this.toJSON();
let result = new Dashboard();
result.init(json);
return result;
}
}
Any idea why JSON.stringify() calls the toJSON() method with the "0" parameter?
The method toJSON will be called with one argument, which is the property name to which this is assigned. In essence, the value of your interest is not that argument, but this, which will be bound to the value that you could transform. Since you call stringify with an array, toJSON will get called with the enumerable properties of that array, i.e. 0 and 1, while this will be the corresponding Dashboard object.
Also, I have the impression you could make good use of Object.assign which will copy properties from one object to another, which is essentially what you do in the constructor's for loop.
So here is how you could do it. I removed the typescript decoration and used plain JavaScript, but the principle remains the same:
class Dashboard {
constructor(data) {
// Object.assign does essentially what you want with the loop:
Object.assign(this, data);
}
init(data) {
return Object.assign(this, data);
}
static fromJS(data) {
return new Dashboard(data);
}
toJSON(key) {
// `key` is the key/index of the property in the parent object.
// That probably is of no interest to you. You need `this`.
// Extract properties into plain object, and return it for stringification
return Object.assign({}, this);
}
clone() {
return new Dashboard(this);
}
}
console.log(JSON.stringify([
Dashboard.fromJS({
code: "1212312",
name: "tresads",
description: "some description"
}),
Dashboard.fromJS({
code: "1212312",
name: "tresads",
description: "some description"
})
]));
Actually, in the given example, you don't need toJSON at all, since the properties of the Dashboard instance are enumerable, and so they will be stringified anyway. If for some reason you do need it for a certain transformation to happen, then of course you still need to include the logic for that transformation, since Object.assign is just a simple copy.

Categories

Resources