I have a problem to solve involving default parameters and object destructuring.
I have an object 'product' with this shape:
{
name: "Slip Dress",
priceInCents: 8800,
availableSizes: [ 0, 2, 4, 6, 10, 12, 16 ]
}
Here is my code so far, but I am receiving an error that 'availableSizes' is not iterable. Can someone help me correct this code?
I have tried adjusting the default parameters in my function and I have moved my return statements to no avail.
function checkIfSizeIsAvailable(product = {availableSizes:[]}, size = 0) {
// let availableSizes = product;
let foundSize = "";
for (let sizeCheck of product.availableSizes) {
if (sizeCheck === size) {
foundSize = size;
}
}
if (foundSize === ""){
return false;
} else {
return true;
}
//for (let i = 0; i < sizes.length; i++) {
// return false;
}
As VLAZ mentioned in a comment, you can pass an object without an availableSizes field and it'll cause that error.
Destructuring happens when your variables together form an object/array on the left-hand size of the assignment:
function checkIfSizeIsAvailable(product = {availableSizes:[]}, size = 0) {
// ^ Because of the default parameter, product always exists
// Now we actually destructure with a default value for missing fields
const { availableSizes = [] } = product;
}
or more compact:
function checkIfSizeIsAvailable({ availableSizes = [] } = {availableSizes:[]}, size = 0) {
}
Mind that this does not defend against non-array values, or even falsy values. A user can pass { availableSizes: false } and your availableSizes would also be false.
Related
It is possible to configure functions as strings to parse them to functions during runtime.
The following example functionAsString expects input and deals with it, I only know that it MUST return a boolean ( I'm expecting that )
const x = {
fields: {
age: 0
}
};
const y = {
fields: {
age: 1
}
};
const functionAsString = "(left, right) => left.fields.age < right.fields.age";
const compareFunction = new Function(functionAsString);
const isXLessThanY = compareFunction(x, y);
if (isXLessThanY === undefined) {
console.error("it should not be undefined...");
} else {
console.log({
isXLessThanY
});
}
isXLessThanY is undefined. Do you know how to setup a valid function based on a string?
The Function constructor actually takes more structured data than eval, it takes the parameter names, then the body. Also it does generate a regular function (not an arrow function) so you have to explicitly return.
const x = {
fields: {
age: 0
}
};
const y = {
fields: {
age: 1
}
};
const functionAsString = "return left.fields.age < right.fields.age";
const compareFunction = new Function('left', 'right', functionAsString);
const isXLessThanY = compareFunction(x, y);
if (isXLessThanY === undefined) {
console.error("it should not be undefined...");
} else {
console.log({
isXLessThanY
});
}
you can use eval() function to run js code as a string, but it has some security issues.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval
const judgeVegetable=function(vegetables,metric){
var max=0,position=0,i=0;
if(metric ==='redness'){
for(i=0;i<vegetables.length;i++){
//for(let veg in vegetables) {
if(vegetables.redness[i] > max){
max=vegetables.redness[i];
position=i;
}
}
}
If I call the function with the parametres below then ;
const vegetables = [
{
submitter: 'Old Man Franklin',
redness: 10,
plumpness: 5
},
{
submitter: 'Sally Tomato-Grower',
redness: 2,
plumpness: 8
},
{
submitter: 'Hamid Hamidson',
redness: 4,
plumpness: 3
}
]
const metric = 'redness';
console.log(judgeVegetable(vegetables, metric));
OUTPUT SHOULD BE :Old Man Franklin (my code is giving error and i don't know where)
Running your code as is gives the following error:
SyntaxError: Unexpected end of input
That's because you forgot the closing brace for the function definition. Procure to use proper indentation and spacing on your code, that way you will spot these kind of errors easily. Example:
const judgeVegetable = function(vegetables,metric) {
var max = 0, position = 0, i = 0;
if(metric ==='redness') {
for(i = 0; i < vegetables.length; i++) {
//for(let veg in vegetables) {
if(vegetables.redness[i] > max) {
max=vegetables.redness[i];
position=i;
}
//}
}
}
Notice also that I included the disabled for with its corresponding disabled closing brace.
But fixing that alone won't produce desired results yet, because there are other errors in your code. I walk you through:
You have the second for loop disabled via comment, let's remove it completely so it doesn't confuse you.
You cannot access vegetables.redness[i] because vegetables is the array, not redness, which is a field inside an object inside the vegetables array. So let's fix that and remove the disabled for loop:
const judgeVegetable = function(vegetables,metric) {
var max = 0, position = 0, i = 0;
if(metric === 'redness') {
for(i = 0; i < vegetables.length; i++) {
if(vegetables[i].redness > max) {
max = vegetables[i].redness;
position = i;
}
}
}
}
Now the code doesn't produce any errors, but it also doesn't return anything, you need to return the value found, so:
const judgeVegetable = function(vegetables,metric) {
var max = 0, position = 0, i = 0;
if(metric === 'redness') {
for(i = 0; i < vegetables.length; i++) {
if(vegetables[i].redness > max) {
max = vegetables[i].redness;
position = i;
}
}
}
return vegetables[position].submitter;
}
And now is working :) Well, almost, you still have to write the code to select the proper answer if the metric is plumpness. Which leads me to a simplification to your code which also solves this issue: remove the if and access the proper field by using the metric parameter:
const judgeVegetable = function(vegetables, metric) {
var max = 0, position = 0, i = 0;
for(i = 0; i < vegetables.length; i++) {
if(vegetables[i][metric]> max) {
max = vegetables[i][metric];
position = i;
}
}
return vegetables[position].submitter;
}
console.log(judgeVegetable(vegetables, 'redness')); // Old Man Franklin
console.log(judgeVegetable(vegetables, 'plumpness')); // Sally Tomato-Grower
Other suggestions for you to consider:
there's no need to assign a function to a variable (const in this case), you can define the function the traditional way:
function judgeVegetable(vegetables, metric) { ...
Or with the new arrow functions syntax:
const judgeVegetable = (vegetables, metric) => { ...
the i parameter can be declared and initialized in the for loop, so no need to do it beforehand
don't use var, use const and let appropriately
You can use the Array.reduce() method, or sort the array then get the first element.
For what's wrong with your code, [i] should be after vegetables instead of vegetables.redness. vegetables is an array and does not have a property named redness. You are also missing a closing } for if(metric ==='redness'){ and missing the return value.
const vegetables = [
{
submitter: 'Old Man Franklin',
redness: 10,
plumpness: 5
},
{
submitter: 'Sally Tomato-Grower',
redness: 2,
plumpness: 8
},
{
submitter: 'Hamid Hamidson',
redness: 4,
plumpness: 3
}
];
const metric = 'redness';
//reduce method
let max = vegetables.reduce((acc,cur)=>{
if(!acc)
{
return cur;
}
if(cur[metric] > acc[metric])
{
return cur;
}
return acc;
},null);
console.log(max.submitter);
//sort method (this mutate the array)
max = vegetables.sort((a,b)=> b[metric] - a[metric])[0];
console.log(max.submitter);
//Your method
const judgeVegetable = function(vegetables,metric){
var max=0,position=0,i=0;
if(metric ==='redness'){
for(i=0;i<vegetables.length;i++){
if(vegetables[i].redness > max){
max = vegetables[i].redness;
position = i;
}
}
}
return vegetables[position].submitter;
}
console.log(judgeVegetable(vegetables,metric));
i have two objects, a master and a temp
the master looks like
{
"gnome":{
"child":{
name:"child",
race:"gnome"
},
"youngling":{
name:"youngling",
race:"gnome"
}
},
"human":{...},
...
}
and the temp looks like
{
"gnome":{
"man":{
name:"man",
race:"gnome"
}
}
what i am trying to do is have the temp be added to the master like
{
"gnome":{
"child":{...},
"youngling":{...},
"man":{...}
},
"human":{...},
...
}
what i currently have
let obj = {}
function generateJson() {
let race = getinput("race").value
let name = getinput("name").value
let temp = {}
temp[`${race}`] = {}
temp[`${race}`][`${name}`] = {
name: name,
race: race
}
obj = Object.assign(obj, temp)
}
all it does is empties and override the first duplicate key with the temp value
a.e. {gnome:{man:{..}}}
earlier this question was closed because it should have been awnsered with How can I merge properties of two JavaScript objects dynamically?
which sadly it didn't, all of the solutions override objects within objects, i want to add to similar keys
Based on your example, this should work
<script>
function merge_without_override(master, temp) {
for(var key in temp) {
if( !temp.hasOwnProperty(key) )
continue;
if(master[key] !== undefined) // key already exists in master.
continue;
master[key] = Object.assign(temp[key]); // key doesnt exist, assign it.
}
}
var master = {
"gnome":{
"child":{
name:"child",
race:"gnome"
},
"youngling":{
name:"youngling",
race:"gnome"
}
},
"human":{}
};
var temp = {
"gnome":{
"man":{
name:"man",
race:"gnome"
}
}
};
console.log("master before merge");
console.log(master);
merge_without_override(master["gnome"], temp["gnome"]);
console.log("master after merge");
console.log(master);
</script>
Output (in jsfiddle):
{
gnome: {
child: { ... },
man: { ... },
youngling: { ... }
},
human: { ... }
}
JsFiddle: https://jsfiddle.net/h10fpcx2/
Chris Ferdinandi wrote a helper for this here;
I've added an updated version below, but wanted to add a fix for my biggest frustration with Object.assign.
It mutates the source material. To fix this, you can add an empty object as the first argument of the deepAssign function, or use function copyDeepAssign below.
// mutates the source material
function deepAssign(...args) {
// Make sure there are objects to merge
const len = args.length;
if (len < 1) return;
const main = args[0];
if (len < 2) return main
// Merge all objects into first
let i = 0,
curr;
while (i < len) {
curr = args[i];
for (var key in curr) {
// If it's an object, recursively merge
// Otherwise, push to key
if (Object.prototype.toString.call(curr[key]) === '[object Object]') {
main[key] = deepAssign(main[key] || {}, curr[key]);
} else {
main[key] = curr[key];
}
}
i++;
}
return main;
}
// Doesn't mutate the source material
function copyDeepAssign(...args) {
const base = {};
return deepAssign(base, ...args);
}
I’m trying to replicate a very simple function that I can get to work with arrays but not with objects. I just want to be able to run a function that logs the next object number as with the numbers array.
Take this working array as an example:
var numbers = [4,2,6],
count = 0;
incrementArr();
function incrementArr() {
if (count < numbers.length) { // if not last array element
console.log(numbers[count]);
count++;
} else {
console.log(numbers[0]);
count = 1;
}
}
Whenever you run the incrementArr function, it’ll just log the next number and then return to the start if the current state (count) is at the end.
However, I cannot replicate the same principle with this object list:
var objs = {
first: { // doesn't have to have 'first'
"number": 4
},
second: { // doesn't have to have 'second'
"number": 2
},
third: { // doesn't have to have 'third'
"number": 6
}
},
count = 0;
incrementObj();
function incrementObj() {
if (count < Object.keys(objs).length) { // if not last obj element
//objs["first"].number
console.log(objs[count].number);
count++;
} else {
console.log(objs["first"].number); // what if no "first" in objects?
count++;
}
}
How could the incrementObj function work the same way that the previous incrementArr function works?
It seems that I can’t pick the specific object instance (e.g. numbers[1] from the array would pick the 2nd number, but only objs[“second”].number would pick the 2nd object, which isn’t iterable if you know what I mean). How could I get a workaround for typical circumstances like this?
So essentially, what’s the difference between this:
first: { // doesn't have to have 'first'
"number": 4
}
and:
{ // doesn't have to have 'first'
"number": 4
}
Why have the "first" etc? (called the key?)
Is there generally a better way of going about object lists (it's difficult to explain)? Thanks for any advice here.
You could take a closure over the object and get the keys and store an index. The returned function get the value and increment and adjusts the index.
function increment(object) {
var keys = Object.keys(object),
index = 0;
return function() {
var value = object[keys[index]].number;
index++;
index %= keys.length;
return value;
};
}
var objs = { first: { number: 4 }, second: { number: 2 }, third: { number: 6 } },
incrementObj = increment(objs);
console.log(incrementObj());
console.log(incrementObj());
console.log(incrementObj());
console.log(incrementObj());
Try this, it access keys through the array generated from keys, objects are unordered list that means you will have to at least order the keys and access them in the array order.
const keysArr = Object.keys(objs);
function incrementObj() {
if (count < keysArr.length) { // if not last obj element
//
console.log(objs[keysArr[count]].number);
count++;
} else {
console.log(objs["first"].number); // what if no "first" in objects?
count++;
}
}
I propose using iterators
See this codepen
If your object have specific shapes, then you use this as a lens to find the number property you want. I'm not sure how you want to use the iterator and have return both the key and the value as separate properties, but you can as well return { [keys[nextIndex]]: values[nextIndex] } or find other shape (the world is your oyster).
Provided you go this length, why not try use RxJs to make your object an observable?
var objs = {
first: { // doesn't have to have 'first'
"number": 4
},
second: { // doesn't have to have 'second'
"number": 2
},
third: { // doesn't have to have 'third'
"number": 6
}
}
function propertyIterator(obj) {
const keys = Object.keys(obj)
const values = Object.values(obj)
const length = keys.length
let nextIndex = 0
return {
next: function() {
const value = {
key: keys[nextIndex],
value: values[nextIndex]
}
let done = false
if (nextIndex >= length) {
done = true
}
nextIndex += 1
return { current: value, done: done}
}
}
}
const incrementObj = propertyIterator(objs)
let result = incrementObj.next()
console.log(result.current.key, result.current.value.number || NaN)
result = incrementObj.next()
console.log(result.current.key, result.current.value.number || NaN)
result = incrementObj.next()
console.log(result.current.key, result.current.value.number || NaN)
using generators, see this codepen:
const objs = {
first: { // doesn't have to have 'first'
"number": 4
},
second: { // doesn't have to have 'second'
"number": 2
},
third: { // doesn't have to have 'third'
"number": 6
}
}
const inc = defaultValue => prop => function* (obj) {
for(let key in obj) {
yield obj[key][prop] || defaultValue
}
}
const getNumber = inc(NaN)('number')
const it = getNumber(objs)
let result = it.next()
while (!result.done) {
console.log(result.value)
result = it.next()
}
I have the following, which works fine. It generates a unique random number for a given empty array and a Max determined by the another array (data) length. I would like to add a check that does:
when the array length is = MaxN, I want to store the last value of the array inside a variable so that if it is = to a new random generated number I will call "generateRandomNumber(array, maxN)" again.
const generateRandomNumber = (array, maxN, lastN) => {
let randomN = Math.floor(Math.random() * maxN) + 0;
console.log(lastN)
if(lastN == randomN) {
// do your thing
}
if(array.includes(randomN)) {
return generateRandomNumber(array, maxN, lastN);
}
if(array.push(randomN) == maxN) {
lastN = array.length - 1
array.length = 0;
}
return randomN
}
export default generateRandomNumber
however I am always getting undefined inside the console.log. I am passing lastN like so:
let lastN;
I would think that that value which is undefined at first would later get updated inside:
if(array.push(randomN) == maxN) {
lastN = array.length - 1
array.length = 0;
}
component where generateRandomNumber is used:
...
const utilityArray = []
const tempQuestions = []
let lastN
class App extends Component {
constructor(props) {
super(props);
this.state = {
collection: gridItemsCollection,
intro: false,
instructions: false,
grid: true,
questions: this.props.questions,
selectedQuestion: ""
}
}
getRandomN = (arr, max, lastN) => {
let s = generateRandomNumber(arr, max, lastN)
return s
}
hideGridItem(e) {
let //index = this.getRandomN(utilityArray, gridItemsCollection.length),
collection = this.state.collection,
newCollection,
//updatedGridItem = collection[index].hidden = true,
questions = this.state.questions.questions,
n = this.getRandomN(tempQuestions, questions.length, lastN);
console.log(lastN)
// this.setState({
// newCollection: [ ...collection, updatedGridItem ]
// })
// if(this.getAnswer(e)) {
this.generateNewQuestion(questions[n])
// }
// else {
// console.log('no')
// }
}
generateNewQuestion(selectedQuestion) {
this.setState({
selectedQuestion
})
}
componentDidMount = () => {
const questions = this.state.questions.questions
let randomNumber = this.getRandomN(tempQuestions, questions.length, lastN)
this.generateNewQuestion(questions[randomNumber])
}
getAnswer = (e) =>
e.target.getAttribute('data-option') == this.state.selectedQuestion.correct_option
render() {
const state = this.state
const { collection, grid, intro, selectedQuestion } = state
console.log(tempQuestions)
return (
<div className="wrapper">
<div className="wrapper-inner">
<View isVisible={state.intro}>
<p> intro screen </p>
</View>
<View isVisible={state.grid}>
<Grid gridItemsCollection={collection}/>
<Question question={selectedQuestion.question} />
<Controls
onClick={this.hideGridItem.bind(this)}
gridItemsCollection={collection}
answers={selectedQuestion}
answer={selectedQuestion.correct_option}
/>
</View>
</div>
</div>
);
}
}
export default App;
It looks like you declare lastN but it is never actually declared for the first time. This means the first time you access it, it'll always be undefined.
You have two options to solve this:
Define lastN to some suitable default value (I would think something like -1 may be suitable based on the code presented).
let lastN = -1;
Just ignore it. From your code, it doesn't look like lastN being undefined should even be a problem since the only check you do is lastN == randomN, which will always be false if lastN is undefined.
It looks like it should get updated, but possibly not on the first call. It looks like it depends on how many questions you have. Unless you have 1 question, it won't update for a few tries. Also, if you have zero questions, it'll never update (since array.push() will be 1 and maxN will be 0).
When you use lastN as a parameter, the local variable lastN takes precedence over global lastN and you are actually updating the local variable, not the global. Just change your argument name.
const generateRandomNumber = (array, maxN, lastN) => {
let randomN = Math.floor(Math.random() * maxN) + 0;
console.log(lastN)
if(lastN == randomN) {
// do your thing
}
if(array.includes(randomN)) {
return generateRandomNumber(array, maxN, lastN);
}
if(array.push(randomN) == maxN) {
lastN = array.length - 1 //-> Here you update the local copy, the global is not affected. So rename you argument to... say lastNArg
array.length = 0;
}
return randomN
}
export default generateRandomNumber
And don't forget to initialize your global lastN var like: let lastN = 0;
So, here it is modified:
const generateRandomNumber = (array, maxN, lastNArg) => {
let randomN = Math.floor(Math.random() * maxN) + 0;
console.log(lastNArg)
if(lastNArg == randomN) {
// do your thing
}
if(array.includes(randomN)) {
return generateRandomNumber(array, maxN, lastNArg);
}
if(array.push(randomN) == maxN) {
lastN = array.length - 1; //make sure lastN is initialized and you have access to it
array.length = 0;
}
return randomN
}
export default generateRandomNumber