0

I am creating a Radio Button object. Each object in the array has the object {value: 1, text: 'Sometext'} and if radio button is selected, to add selected: true into the object and remove selected from the others.

const onChoiceChange = function setChoiceStateAndUpdateSelectedObject(e) {
    let updating = []
    choices.map((item, index) => {
        if (item.value == e.target.value) {
            updating.push({...item, selected: true})
        } else {

                item.selected && updating.push({selected, ...item} = item) //here it says selected undefined even with the condition check          
                !item.selected && updating.push(item)
        }
    })
    setChoices(updating)
    console.log('updating', updating)
}

I use the code snippet tool here and it works, however when I compiled my codes in NextJS it gives me the following error.

I'm using the same code in snippet, I don't understand why it says selected is undefined when I select a different radio button second time.

Error Message

let choices = [
{ value: 1, text: 'This is value 1' },
{ value: 2, text: 'This is value 2' },
{ value: 3, text: 'This is value 3' },
{ value: 4, text: 'this is value 4'}
]

document.addEventListener('input',(e)=>{
  let updating = []
if(e.target.getAttribute('name')=="choices") {


  choices.map((item, index) => {
    if (item.value == e.target.value) {
          updating.push({...item, selected: true})
    } else {
          item.selected && updating.push({selected, ...item} = item)           
          !item.selected && updating.push(item)
    }
  })
}
console.log(updating)
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>


<input type='radio' name='choices' value='1'/>
<input type='radio' name='choices' value='2'/>
<input type='radio' name='choices' value='3'/>
<input type='radio' name='choices' value='4'/>
David Mann
  • 2,074
  • 1
  • 17
  • 17
Someone Special
  • 12,479
  • 7
  • 45
  • 76
  • `selected` isn't defined anywhere in that function. What are you trying to do? Remove the `selected` key-value if `item.selected` is truthy? You should probably also use a `forEach` instead of `map`. – Drew Reese May 23 '20 at 04:28
  • @DrewReese, There's always a value being added to `updating`, it's even one to one. `map` is the correct choice, just used slightly wrong. The pushes should be replaced with returns and the return value of `map` assigned to `updating`. Though `forEach` would also work as you said, I think `map` is a better fit. – David Mann May 23 '20 at 05:06
  • I'm trying to go through all the objects, if it's selected I add a selected: true to it, so backend I can know my option. However since it's a radio button, there's always only one selected object, so I need to remove the selected: true from the rest of my objects. – Someone Special May 23 '20 at 13:42
  • so in my Array.map, first IF condition check if it's selected, if selected, add a selected: true, the ELSE statement states if there's a selected: true key, to remove it from the object, and if there's no selected: true key, to just return the object to new array – Someone Special May 23 '20 at 13:43
  • So .. I tried the same codes, it works in code snippets, I don't know why compiler showing error. – Someone Special May 23 '20 at 13:44
  • @SomeoneSpecial FYI, your full snippet is possibly misleading. Since you added (but aren't using) react, I'm guessing that the choices object is meant to represent a maintained state. A lot of the answers aren't removing selected from the choices because your example always starts with a clean choices array. There's no `selected` to remove, but if it's using a maintained state, the old selected value would actually still be there. It makes the examples based off of yours look like they would work, but quite likely won't when they're used in your full code. – David Mann May 23 '20 at 18:39
  • The simplified snippet would also explain why it works for you here, but nextjs throws the error. You never hit the case where you have an inactive item that has `selected: true` in you first run (or ever in the snippet) but it will occur on any runs after the first, exposing the error. You could simulate a second run by including `selected: true` in one of the items in `choices`. – David Mann May 23 '20 at 18:53

4 Answers4

1

Okay, so selected is unknown variable/entity to the parser because you haven't defined anywhere in the code. selected:item.selected will make selected:true.

Please find the working snippet below:

let choices = [{
    value: 1,
    text: 'This is value 1'
  },
  {
    value: 2,
    text: 'This is value 2'
  },
  {
    value: 3,
    text: 'This is value 3'
  },
  {
    value: 4,
    text: 'this is value 4'
  }
]

document.addEventListener('input', (e) => {
  let updating = []
  if (e.target.getAttribute('name') == "choices") {
    choices.map((item, index) => {
      if (item.value == e.target.value) {
        updating.push({ ...item,
          selected: true
        })
      } else {
        item.selected && updating.push({
          selected:item.selected,
          ...item
        } = item) 
        !item.selected && updating.push(item)
      }
    })
  }
  console.log(updating)
})
<html>

<head>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
</head>

<body>
  <input type='radio' name='choices' value='1' />
  <input type='radio' name='choices' value='2' />
  <input type='radio' name='choices' value='3' />
  <input type='radio' name='choices' value='4' />
</body>

</html>
halfer
  • 19,824
  • 17
  • 99
  • 186
Amulya Kashyap
  • 2,333
  • 17
  • 25
  • so `{ selected:item.selected, ...item } ` make no sence , as youselected property is already in item , then you are over writing it with `...item` – Vivek Doshi May 23 '20 at 04:39
  • Sir, you cannot address property without its object, that's why selected is showing undefined error. the parser is not able to recognize what selected is? – Amulya Kashyap May 23 '20 at 04:44
  • Or, you can directly push the item `item.selected && updating.push({...item} = item)`. – Amulya Kashyap May 23 '20 at 04:46
  • Yes, but there no need of `selected` field , as you are already setting up from `item` object it self, this will work `updating.push({ ...item }) ` simply – Vivek Doshi May 23 '20 at 04:46
0

please update your line inside else block to

                item.selected && updating.push(item) //         
Tulshi Das
  • 480
  • 3
  • 18
  • I thought this too, initially, but if `item.selected` is truthy then it is already on `item` and will be spread in. I'm not sure what the OP is trying to do. – Drew Reese May 23 '20 at 04:35
  • so `{ selected:item.selected, ...item } ` make no sence , as youselected property is already in item , then you are over writing it with `...item` – Vivek Doshi May 23 '20 at 04:39
  • 1
    @TulshiDas, this also does the same, you only need `updating.push({...item})` – Vivek Doshi May 23 '20 at 04:43
  • Which does (*nearly*) the same thing as the `!item.selected` branch. Waiting for OP to respond to comment or provide more clarity/context. – Drew Reese May 23 '20 at 04:50
  • I want to have selected: true in the object if it's selected, but I don't want selected: false in the object if it has been deselected if not I end up with arrays of objects, some with selected: false, some without selected at all. So if it's deselected, I want it to be just {value, text} hence I wanted the remove the selected key with {...selected, item} => .item. If selected for that object has been set to true (when it was selected by radio), it should be removed as there should only be one object selected at anyone time. – Someone Special May 23 '20 at 13:40
  • Is my concept weird? – Someone Special May 23 '20 at 13:40
  • @SomeoneSpecial, your concept isn't weird at all. It's just not clear in your question or your code that this is what you're trying to do. – David Mann May 23 '20 at 18:05
0

You're mixing the rest and spread syntax up. Both of them are an ellipse (...), but are basically opposites of each other.

In your true case you're using spread, {...item, selected: true}, to set all of the values in item to the new object, and then adding the key selected (potentially overriding a previous selected key from item). Works fine.

In your else case you're using rest (and by extension, destructuring, {selected, ...item} = item. Destructuring (and rest) are asignment only. Destructuring creates new variables or, if wrapped in parenthesis, reassigns old variables. Rest, in particular, means "take all of the entries I didn't ask for by name and shove them into a new object called [blank]". You are not adding a new object to an array. You are assigning the value of items.selected to an undefined variable, selected, and attempting to reassign the rest of the entries in item to a new object ...also called item. This won't work. Ever. If you're trying to remove item.selected, then you could use destructuring like so:

...
} else {
    let {selected, ...cleanedItem} = item;

    item.selected && updating.push(cleanedItem);
    !item.selected && updating.push(item);
}
...


You could also delete selected from item in your else case. delete does mutate the original object, which is usually best avoided, but if you know it's safe (depends on the rest of your code), it's a valid option. Here's an example:

...
} else {
    delete item.selected;
    updating.push(item);
}
...


A third way to remove selected is to do the same thing you do in your true case, but settingselectedtoundefined`:

...
} else {
    updating.push({...item, selected: undefined});
}
...


Though, if you go that route you may as well get rid of the if all together and use a ternary operator (expresion ? [true case] : [false case]) instead. It can't do complex logic inside of the two cases, but you don't need to. That would look like this:

...
    // replacing the if/else
    updating.push({
        ...item, 
        selected: item.value == e.target.value
            ? true
            : undefined
    });
...

Or, if false is valid for selected you can skip any conditional branching:

...
    // replacing the if/else
    updating.push({
        ...item, 
        selected: item.value == e.target.value
    });
...


While we're at it, you may want to drop pushing to the updating array all together. Map, unlike forEach, is designed to return an array. Just return the value you want set for that item in the array:

const onChoiceChange = function setChoiceStateAndUpdateSelectedObject(e) {
    let updating = choices.map((item, index) => {
        return {
            ...item, 
            selected: item.value == e.target.value
        }
    });
    setChoices(updating)
    console.log('updating', updating)
}


Putting it all together (collapsed, because this answer is long as it is):

let choices = [
  { value: 1, text: 'This is value 1' },
  { value: 2, text: 'This is value 2' },
  { value: 3, text: 'This is value 3' },
  { value: 4, text: 'this is value 4' }
];

document.addEventListener('input', (e) => {
  let updating = [];

  if (e.target.getAttribute('name') == "choices") {
    updating = choices.map(item => ({
      ...item,
      selected: item.value == e.target.value
    }));
  }
  console.log(updating)
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

<input type='radio' name='choices' value='1' />
<input type='radio' name='choices' value='2' />
<input type='radio' name='choices' value='3' />
<input type='radio' name='choices' value='4' />



As a parting note, the reason for the syntax error in the first place. It's mentioned above in the part about destructuring, but I figured I'd address it directly. When you destructure, you need to either use a declarative key word (let, const, orvar`) to create a new variable, or use existing variables and parenthesis. You cannot mix and match, you need to either need to use all new variables or reuse all existing variables. Like so:

// new variables
//  - can't be the same name as a defined variable in scope
let {selected, ...cleanedItem} = item;

// or with existing variables
//  - the existing variables must be in scope
//  - we'll assume item is in scope, same as your examples
let selected;

({selected, ...item} = item);

So the reason you get the syntax error is because you're trying to mix new and old variables together. selected (as others have noted) isn't defined. Along with that (though no error would be thrown) if you try to return the result of a destructure, you actually return the unmodified original item you are destructuring from. So selected wouldn't be removed at all, even if the destructuring syntax was used correctly. Fun, no?

David Mann
  • 2,074
  • 1
  • 17
  • 17
  • I want to have selected: true in the object if it's selected, but I don't want selected: false in the object if it has been deselected if not I end up with arrays of objects, some with selected: false, some without selected at all. So if it's deselected, I want it to be just {value, text} – Someone Special May 23 '20 at 13:37
  • @SomeoneSpecial, I updated my answer to reflect that you're trying to remove `selected` from `item` (if it's not the active option). I also added a bit more detail about why the error occurred in the first place. It's a bit long now, but better too much information, than not enough right? – David Mann May 23 '20 at 17:57
  • Looking closer, and based on your comments, I think you're actually using rest and spread correctly, it's just the attempt at using destructuring and rest inline (and mixing old and new variables) that's tripping you up. I left my assumptions in the answer unchanged, since using rest "looks" like a mistake and others may benefit. – David Mann May 23 '20 at 18:46
0

else case you defined is not logically correct. Map function returns an object of a type which is being mapped. so u can directly assign the return to a variable.

let updating = choices.map((item, index) => { /* logic  */}

and in else you can simply push to updating array or as mentioned above you can simply return the item from the map.

let updating = choices.map((item, index) => {
    if (item.value == e.target.value) {
        return {...item, selected: true};
    } else {
        return item;
     // can push as below if you are not returning from map
     // updating.push(item);
});
  • There should only be one object with selected: true since it's a radio button, need to remove the key from other objects if it has been selected before. – Someone Special May 23 '20 at 13:47