I have a function which has a default configuration stored in an object. The function takes optional argument "options", if there is any configuration in the options argument, it should overwrite the default config.
This is my implementation:
const myFunction = (props) => {
const config = {
slopeModifier: 2,
minDistance: 30,
lockScrollIfHorizontal: true,
callback: null,
};
if (props.options) {
for (const property in options) {
config[property] = options[property];
}
}
};
export default myFunction;
in other file I would do something like this:
import myFunction
const options = {minDistance: 50, slope: 3};
myFunction({arg1, arg2, options});
Is this solution a good practice? Is there a standard way how to store default values and overwrite them with optional argument?
Also, I am getting an eslint(guard-for-in) warning, could my code cause any bugs?
Is there a standard way how to store default values and overwrite them with optional argument?
Yes, there's a standard way to do this, via Object.assign
const myFunction = (props) => {
const config = Object.assign({
slopeModifier: 2,
minDistance: 30,
lockScrollIfHorizontal: true,
callback: null,
}, props.options);
// ...
};
export default myFunction;
There's also a newer Rest/Spread syntax available, which Alberto's answer shows.
const config = {
slopeModifier: 2,
minDistance: 30,
lockScrollIfHorizontal: true,
callback: null,
... props.options
};
Is this solution a good practice?
Overriding default config this way is fine. My concern would be with putting other arguments on your options object. If your other arguments are required, you might want to make them official parameters in their own right, and have the options be a last (optional) parameter.
const myFunction = (arg1, arg2, props) => ...;
const options = {minDistance: 50, slope: 3};
myFunction(arg1, arg2, options);
Also, I am getting an eslint(guard-for-in) warning, could my code cause any bugs?
It could. As the docs point out, it's possible that someone could add properties to the prototype for whatever object type you pass in as your options, and you'd be picking up those prototype properties in addition to whatever properties were added directly to the object. This would be bad practice, but it's not unheard of:
// someone else's code, maybe in a third-party library that thought it was being clever.
Object.prototype.foo = "bar";
// Your code
for(const prop in {"1": 2}) { console.log(prop); }
Output:
1
foo
You could use the spread operator to override properties if the props.options exist in the function call, like this:
const myFunction = (props) => {
const config = {
slopeModifier: 2,
minDistance: 30,
lockScrollIfHorizontal: true,
callback: null,
...props.options
};
};
export default myFunction;
You are violating the Single Responsibility Principle, and are better off creating a class to set, update, and return your config code:
class configClass{
config = {
slopeModifier: 2,
minDistance: 30,
lockScrollIfHorizontal: true,
callback: null
}
getConfig(){
return this.config
}
updateConfig(options){
for(const property in options) {
this.config[property] = options[property]
}
}
}
//export default configClass
//import configClass as myClass
const myClass = new configClass()
const options = {minDistance: 50, slope: 3}
const obj = {arg1:"foo", arg2:"bar", options: options}
myClass.updateConfig(obj.options)
console.log(myClass.getConfig())
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.26.0/babel.min.js"></script>
You can call this function without any parameters and the default values will be applied:
const myFunction = ({
slopeModifier = 2,
minDistance = 30,
lockScrollIfHorizontal = true,
callback = null
} = {}) => {
...
};
export default myFunction;
On your other file, you can call the function like this:
import myFunction
myFunction({minDistance: 10}); // or myFunction();
Example
const myFunction = ({
slopeModifier = 2,
minDistance = 30,
lockScrollIfHorizontal = true,
callback = null
} = {}) => {
console.log(slopeModifier, minDistance);
};
myFunction();
myFunction({minDistance: 10});
Related
Is it possible for a JS field decorator to change its value?
A simplified use case would be something like this:
const addItem = (newValue) => {
return function (target) {
target.value.push(newValue);
};
};
class Test {
#addItem(4)
static values = [1,2,3];
}
const test = new Test();
console.log(test.constructor.values) // [1,2,3,4]
Using the following experimental decorators:
'#babel/plugin-proposal-decorators',
{
version: '2018-09',
decoratorsBeforeExport: true,
},
End goal is to make a decorator to inject tailwind style sheets into a lit elements static styles. Currently using a mixin for this but just doing this for fun and to learn whats possible with decorators.
Update to Barmars comments
When trying to return a value from the inner function, I end up getting an error:
export const addItem = (value) => {
return function (target) {
return [value];
};
};
Uncaught TypeError: An element descriptor's .kind property must be either "method" or "field", but a decorator created an element descriptor with .kind "undefined"
Looking at the documentation, the variables getting passed to each of these functions doesn't seem to match either.
function logged(value, { kind, name }) {
if (kind === "field") {
return function (initialValue) {
console.log(`initializing ${name} with value ${initialValue}`);
return initialValue;
};
}
}
When running that example, the 2nd parameter to logged() is undefined. "initialValue" also is an object, not the value:
Object { kind: "field", key: "styles", placement: "own", descriptor: {…}, initializer: value(), … }
Nicolo Ribaudo was able to help me over on Babel's discussions. The correct way to do this is to use the initializer function:
const addItem = (newValue) => {
return function (target) {
const { initializer } = target;
target.initializer = function () {
return [
...initializer.call(this),
newValue,
];
};
};
};
class Test {
#addItem(4)
static values = [1,2,3];
}
const test = new Test();
console.log(test.constructor.values) // [1,2,3,4]
here is by JS code:
var a = { depth: object_with_many_attributes.depth };
notice how depth appear twice, any standard way to rewrite by using depth just once?
You can use Object Destructuring, but in that case, you need to define additional variables, like in an example
const object_with_many_attributes = { depth: 'xxx', 'somethingOther': 'qwerty'};
const { depth, somethingOther } = object_with_many_attributes;
const a = { depth, somethingOther };
If depth must be used only once, you can destructure it with key:
// assignment
var object_with_many_attributes = { depth: 'foo', bar: 123 };
// destructure it:
var { 'depth' : a } = object_with_many_attributes;
console.log('object a:', { a }); // use as object later
When composing objects with functions, what's the proper way to pass parameters within the composition object?
For example:
// functions
const sayHi = () => {
console.log('Hi!');
};
const greetPerson = (name) => {
console.log(`Hello there, ${name}!`);
};
// "composition" objects, referring to the functions above
const hi = {
'id': 1,
'speak': sayHi
};
const greetWorld = {
'id': 2,
'speak': greetPerson('World') // how do I go about passing the function a parameter here?
};
const greetMars = {
'id': 3,
'speak': greetPerson('Mars')
};
// register and run each
const salutations = [hi, greetWorld, greetMars];
for (let salutation of salutations) {
salutation.speak();
}
The output when I run this is:
$ node sayHi.js
Hello there, World!
Hello there, Mars!
Hi!
/Users/rotarydial/sayHi.js:21
salutation.speak();
TypeError: salutation.speak is not a function
How do I handle this properly so I can pass greetPerson() a parameter in the greet object?
In the code, greetPerson('World'), greetPerson('Mars') were getting executed at the time of object creation only.
const greetWorld = {
'id': 2,
'speak': greetPerson('World')
};
was getting evaluate to below after execution
const greetWorld = {
'id': 2,
'speak': undefined // as it was getting execute immediately, and function by default returns undefined if nothing explicitly returned from it.
};
Fix -
// functions
const sayHi = () => {
console.log('Hi!');
};
const greetPerson = (name) => {
console.log(`Hello there, ${name}!`);
};
// "composition" objects, referring to the functions above
const hi = {
'id': 1,
'speak': sayHi
};
const greetWorld = {
'id': 2,
'speak': () => greetPerson('World') // how do I go about passing the function a parameter here?
};
const greetMars = {
'id': 3,
'speak': () => greetPerson('Mars')
};
// register and run each
const salutations = [hi, greetWorld, greetMars];
for (let salutation of salutations) {
salutation.speak();
}
I was able to get this working by declaring the function with parameter as an anonymous function, like this:
'speak': () => greetPerson('World')
I think this may be what you're looking for:
// functions
const sayHi = () => {
console.log('Hi!');
};
const greetPerson = (name) => {
console.log(`Hello there, ${name}!`);
};
// "composition" objects, referring to the functions above
const hi = {
'id': 1,
'speak': sayHi
};
const greet = {
'id': 2,
'speak': greetPerson // pass in a reference to the function for later use.
};
// register and run each
const salutations = [hi, greet];
for (let salutation of salutations) {
salutation.speak("World!");
}
I suggest you read the MDN Working with Objects page, particularly the section Defining Methods
Here's an example from that page:
objectName.methodname = functionName;
var myObj = {
myMethod: function(params) {
// ...do something
}
// OR THIS WORKS TOO
myOtherMethod(params) {
// ...do something else
}
};
Today I came across the following syntax which I didn't recognize:
const createUser = ({
age = 1,
name = 'Anonymous',
}) => ({
age,
name,
});
const defaultP = createUser({
age: 5
});
console.log(defaultP);
I think it uses Object destructuring and default parameters in order to set defaults of the object which is send as an argument.
The syntax threw me a bit off because normally I see object destructuring only in the following manner:
let obj = {
prop1: 1
}
const {prop1} = obj;
console.log(prop1);
Question:
How does this syntax work exactly?
That syntax indeed uses Object Destructuring in order to extract default values from the parameter object. There are some examples in the Mozilla documentation that helps us understand the trick, check this out:
var {a = 10, b = 5} = {a: 3};
console.log(a); // 3
console.log(b); // 5
A possible disadvantage of your example is that the createUser method ignores all other values of the parameter object and always returns an object that contains only age and name. If you want to make this more flexible, we could use Object.assign() like this:
const createUser = (o) => Object.assign({ age: 1, name: 'Anonymous' }, o);
In this case, the user created will be an object that merges the parameter object with the default values. Note now that the default values are in the method body. With this method we can create users that contain other properties, example:
const superman = createUser({ name: 'Superman', type: 'superhero' });
console.log(superman);
// output: {age: 1, name: "Superman", type: "Superhero"}
Your code is using both Object Destructuring and default function props.
const createUser = ({
age = 1,
name = 'Anonymous',
}) => ({
age,
name,
});
Here function createUser is accepting single argument of type Object. Function is returing same object, if you have both object properties defined in your argument, then it will return your passed object. Otherwise it will replace it with default values, which are 1 and Anonymous respectively.
You can further read about it here:
https://wesbos.com/destructuring-renaming/
https://wesbos.com/destructuring-default-values/
If you use babel and transpile your code to ES5, it will look like this:
function createUser(params) {
return {
age: typeof params.age === 'undefined' ? 1 : params.age,
name: typeof params.name === 'undefined' ? 'Anonymous' : params.name,
};
}
Just a note: default values for function arguments works the same way:
const multiply = (a, optionalB) => {
const b = typeof optionalB !== 'undefined' ? optionalB : 2;
return a * b;
}
Is same as:
const multiply = (a, b = 2) => {
return a * b;
}
It increases a readability, mostly in cases when argument is used several times.
I've manage to test Vuex getters that are isolated from other code. I'm now facing some issues when a getter depends on other getters, see the following example:
getters.js
export const getters = {
getFoo(state) => prefix {
return `${prefix}: ${state.name}`;
},
getFancyNames(state, getters) {
return [
getters.getFoo('foo'),
getters.getFoo('bar')
]
}
}
getters.spec.js
import { getters } = './getters';
const state = {
name: 'stackoverflow'
};
describe('getFoo', () => {
it('return name with prefix', () => {
expect(getters.getFoo(state)('name')).toBe('name: stackoverflow');
});
});
describe('getFancyNames', () => {
// mock getters
const _getters = {
getFoo: getters.getFoo(state)
}
it('returns a collection of fancy names', () => {
expect(getters.getFancyNames(state, _getters)).toEqual([
'foo: stackoverflow',
'bar: stackoverflow'
]);
});
});
When the tested getter depends on other getter that has arguments this means that I've reference the original getter.getFoo on the mock, and this breaks the idea of mocking, since the tests start to have relation with each other. When the getters grow, and the dependency graph has several levels it makes the tests complex.
Maybe this is the way to go, just wanted to check that I'm not missing anything...
I agree with you that referencing the actual collaborator in your mock defeats the purpose of a mock. So instead I would simply directly return whatever you want your collaborator to return.
In your example, instead of doing something like this:
// mock getters
const _getters = {
getFoo: getters.getFoo(state)
}
You would simply put in whatever getters.getFoo(state) would return:
const _getters = {
getFoo: 'foobar'
}
If you have a getter that takes an additional argument you would simply return a function that returns a constant:
const _getters = {
getFoo: x => 'foobar',
}
Since I'm using Jest there is an option in the jest mock function that let's specify the return value when called:
mockReturnValueOnce or mockReturnValue
More information can be found here: https://facebook.github.io/jest/docs/en/mock-functions.html#mock-return-values
Using the same code as in the question this could be solved like this:
const state = {
name: 'stackoverflow'
}
describe('getFancyNames', () => {
const getFoo = jest.fn()
getFoo.mockReturnValueOnce('foo: stackoverflow')
getFoo.mockReturnValueOnce('bar: stackoverflow')
it('returns a collection of fancy names', () => {
expect(getters.getFancyNames(state, { getFoo })).toEqual([
'foo: stackoverflow',
'bar: stackoverflow'
])
})
})
A cleaner way that I have found is to create your own mocked getters object. This only works if the getter uses the unaltered state like the question does.
const state = {
name: 'stackoverflow'
}
describe('getFancyNames', () => {
const mockedGetters = {
...getters, // This can be skipped
getFoo: getters.getFoo(state), // We only overwrite what is needed
};
it('returns a collection of fancy names', () => {
expect(getters.getFancyNames(state, mockedGetters)).toEqual([
'foo: stackoverflow',
'bar: stackoverflow'
])
})
})
Extra
In the case that you do need to call other getter functions just pass the mocked getters objects into another mocked getters object. It sounds worse than is actually is.
getters.py
export const getters = {
getBar(state) = { // new extra hard part!
return state.bar,
},
getFoo(state, getters) => prefix {
return `${prefix}: ${state.name} with some ${getters.getBar}`;
},
getFancyNames(state, getters) {
return [
getters.getFoo('foo'),
getters.getFoo('bar')
]
}
}
const _mockedGetters = {
...getters, // This can be skipped
getFoo: getters.getFoo(state), // We only overwrite what is needed
};
const mockedGetters = {
.._mockedGetters, // Use the mocked object!
getBar: getters.getBar(state, _mockedGetters), // We only overwrite what is needed
};
// continue down the line as needed!