Repository: mui/material-ui (TypeScript)
Date: 2018-12-08 17:38:24+00:00
Task type: Bug Fix

Issue:
ToggleButtonGroup onChange should pass empty array instead of null
<!--- Provide a general summary of the feature in the Title above -->
Mixing the types (`Array` / `null`) adds unnecessary complexity.

<!--
    Thank you very much for contributing to Material-UI by creating an issue! ❤️
    To avoid duplicate issues we ask you to check off the following list.
-->

<!-- Checked checkbox should look like this: [x] -->
- [x] This is not a v0.x issue. <!-- (v0.x is no longer maintained) -->
- [x] I have searched the [issues](https://github.com/mui-org/material-ui/issues) of this repository and believe that this is not a duplicate.

## Expected Behavior 🤔
<!---
    Describe how it should work.
-->
`onChange` should pass `[]` instead of `null` when no toggles are selected.

## Current Behavior 😯
<!---
    Explain the difference from current behavior.
-->
`onChange` will either pass `['item1', 'item2', ...]` or `null` when there are not an items selected.

## Examples 🌈
<!---
    Provide a link to the Material design specification, other implementations,
    or screenshots of the expected behavior.
-->

## Context 🔦
<!---
    What are you trying to accomplish? How has the lack of this feature affected you?
    Providing context helps us come up with a solution that is most useful in the real world.
-->

As soon as the developer sees that `onChange` passes an `Array` of selected toggles, their natural assumption is that if none are selected, it will be an empty `Array`.  It's a bit more congruent in terms of type consistency as well.

I ran into a bug in my code and had to cast to an empty `Array`.

It will probably be very common for developers to want to use something like `visibilityToggles.includes('showCurrent')`.

Current situation:

```javascript
handleToggles = (e, toggles) => {
  if ((toggles || []).includes('showCurrent') { /* ... */ } 
}
```
And unfortunately you can't use default function parameters either like this because `null` is considered a valid value:

```javascript
handleToggles = (e, toggles=[]) => {
  if (toggles).includes('showCurrent') { /* ... */ } 
}
```

With the proposed solution it would simplify to:

```javascript
handleToggles = (e, toggles) => {
  if (toggles).includes('showCurrent') { /* ... */ } 
}
```


Issue discussion:

Solution patch:
diff --git a/packages/material-ui-lab/src/ToggleButtonGroup/ToggleButtonGroup.js b/packages/material-ui-lab/src/ToggleButtonGroup/ToggleButtonGroup.js
--- a/packages/material-ui-lab/src/ToggleButtonGroup/ToggleButtonGroup.js
+++ b/packages/material-ui-lab/src/ToggleButtonGroup/ToggleButtonGroup.js
@@ -34,7 +34,6 @@ class ToggleButtonGroup extends React.Component {
     if (value && index >= 0) {
       newValue = [...value];
       newValue.splice(index, 1);
-      if (newValue.length === 0) newValue = null;
     } else {
       newValue = value ? [...value, buttonValue] : [buttonValue];
     }
@@ -120,7 +119,7 @@ ToggleButtonGroup.propTypes = {
    * @param {object} event The event source of the callback
    * @param {object} value of the selected buttons. When `exclusive` is true
    * this is a single value; when false an array of selected values. If no value
-   * is selected the value is null.
+   * is selected and `exclusive` is true the value is null; when false an empty array.
    */
   onChange: PropTypes.func,
   /**
diff --git a/pages/lab/api/toggle-button-group.md b/pages/lab/api/toggle-button-group.md
--- a/pages/lab/api/toggle-button-group.md
+++ b/pages/lab/api/toggle-button-group.md
@@ -21,7 +21,7 @@ import ToggleButtonGroup from '@material-ui/lab/ToggleButtonGroup';
 | <span class="prop-name required">children *</span> | <span class="prop-type">node</span> |   | The content of the button. |
 | <span class="prop-name">classes</span> | <span class="prop-type">object</span> |   | Useful to extend the style applied to components. |
 | <span class="prop-name">exclusive</span> | <span class="prop-type">bool</span> | <span class="prop-default">false</span> | If `true`, only allow one of the child ToggleButton values to be selected. |
-| <span class="prop-name">onChange</span> | <span class="prop-type">func</span> |   | Callback fired when the value changes.<br><br>**Signature:**<br>`function(event: object, value: object) => void`<br>*event:* The event source of the callback<br>*value:* of the selected buttons. When `exclusive` is true this is a single value; when false an array of selected values. If no value is selected the value is null. |
+| <span class="prop-name">onChange</span> | <span class="prop-type">func</span> |   | Callback fired when the value changes.<br><br>**Signature:**<br>`function(event: object, value: object) => void`<br>*event:* The event source of the callback<br>*value:* of the selected buttons. When `exclusive` is true this is a single value; when false an array of selected values. If no value is selected and `exclusive` is true the value is null; when false an empty array. |
 | <span class="prop-name">selected</span> | <span class="prop-type">union:&nbsp;bool&nbsp;&#124;<br>&nbsp;enum:&nbsp;'auto'<br><br></span> | <span class="prop-default">'auto'</span> | If `true`, render the group in a selected state. If `auto` (the default) render in a selected state if `value` is not empty. |
 | <span class="prop-name">value</span> | <span class="prop-type">any</span> |   | The currently selected value within the group or an array of selected values when `exclusive` is false. |
 


Test patch:
diff --git a/packages/material-ui-lab/src/ToggleButtonGroup/ToggleButtonGroup.test.js b/packages/material-ui-lab/src/ToggleButtonGroup/ToggleButtonGroup.test.js
--- a/packages/material-ui-lab/src/ToggleButtonGroup/ToggleButtonGroup.test.js
+++ b/packages/material-ui-lab/src/ToggleButtonGroup/ToggleButtonGroup.test.js
@@ -178,7 +178,7 @@ describe('<ToggleButtonGroup />', () => {
     });
 
     describe('non exclusive', () => {
-      it('should be null when current value is toggled off', () => {
+      it('should be an empty array when current value is toggled off', () => {
         const handleChange = spy();
         const wrapper = mount(
           <ToggleButtonGroup value={['one']} onChange={handleChange}>
@@ -193,7 +193,8 @@ describe('<ToggleButtonGroup />', () => {
           .simulate('click');
 
         assert.strictEqual(handleChange.callCount, 1);
-        assert.strictEqual(handleChange.args[0][1], null);
+        assert.ok(Array.isArray(handleChange.args[0][1]));
+        assert.strictEqual(handleChange.args[0][1].length, 0);
       });
 
       it('should be an array with a single value when value is toggled on', () => {