4

I have the following function defined and run on DOMContentLoaded (among others, not relevant here):

function dependentControls() {
  const dependers = [...document.querySelectorAll('[data-depends-on]')]
  for (const depender of dependers) {
    let dependency = document.getElementById(depender.dataset.dependsOn)
    if (dependency) {
      let dependencyDetails = {
        prop: null,
        state: null
      }
      switch (dependency.type) {
        case 'checkbox':
          {
            dependencyDetails.prop = 'checked'
            dependencyDetails.state = false
            break // this break exits the current for loop iteration
          }
        case 'text':
          {
            dependencyDetails.prop = 'value'
            dependencyDetails.state = ''
            break // this break exits the current for loop iteration
          }
        default:
          console.log('default case')
      }
      console.log("switch...end") // this line is never reached
      depender.disabled = !dependency[dependencyDetails.prop]
      dependency.addEventListener('change', () => {
        console.log('dependancy changed')
        depender.disabled = dependency[dependencyDetails.prop] === dependencyDetails.state
      })
    }
  }
  console.log('for...end')
}

document.addEventListener('DOMContentLoaded', dependentControls);
<div>
  <input type="checkbox" id="fooBar" />
  <label for="fooBar">dependency</label>
</div>
<hr />
<div>
  <label for="fooBaz">depender</label>
  <input type="text" id="fooBaz" data-depends-on="fooBar" value="depender" />
</div>

This is the relevant part of the transpiled code:

function _toConsumableArray(arr) {
  if (Array.isArray(arr)) {
    for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) {
      arr2[i] = arr[i];
    }
    return arr2;
  } else {
    return Array.from(arr);
  }
}

var dependentControls = function dependentControls() {
  var dependers = _toConsumableArray(document.querySelectorAll('[data-depends-on]'));
  
  var _iteratorNormalCompletion5 = true;
  var _didIteratorError5 = false;
  var _iteratorError5 = undefined;

  try {
    var _loop5 = function _loop5() {
      var depender = _step5.value;

      var dependency = document.getElementById(depender.dataset.dependsOn);

      if (dependency) {
        var dependencyDetails = {
          prop: 'checked',
          state: false
        };

        switch (dependency.type) {
          case 'checkbox':
            {
              dependencyDetails.prop = 'checked';
              dependencyDetails.state = false;
              return "break";
            }

          case 'text':
            {
              dependencyDetails.prop = 'value';
              dependencyDetails.state = '';
              return "break";
            }

          default:
        }

        console.log("switch...end"); // this line is never reached

        depender.disabled = !dependency[dependencyDetails.prop];
        dependency.addEventListener('change', function() {
          depender.disabled = dependency[dependencyDetails.prop] === dependencyDetails.state;
        });
      }
    };

    _loop4: for (var _iterator5 = dependers[Symbol.iterator](), _step5; !(_iteratorNormalCompletion5 = (_step5 = _iterator5.next()).done); _iteratorNormalCompletion5 = true) {
      var _ret2 = _loop5();

      switch (_ret2) {
        case "break":
          break _loop4;

        default:
          if (_typeof(_ret2) === "object") return _ret2.v;
      }
    }
  } catch (err) {
    _didIteratorError5 = true;
    _iteratorError5 = err;
  } finally {
    try {
      if (!_iteratorNormalCompletion5 && _iterator5.return != null) {
        _iterator5.return();
      }
    } finally {
      if (_didIteratorError5) {
        throw _iteratorError5;
      }
    }
  }

  console.log("for...end");
}

document.addEventListener('DOMContentLoaded', dependentControls);
<div>
  <input type="checkbox" id="fooBar" />
  <label for="fooBar">dependency</label>
</div>
<hr />
<div>
  <label for="fooBaz">depender</label>
  <input type="text" id="fooBaz" data-depends-on="fooBar" value="depender" />
</div>

This is my .babelrc:

{
  "presets": [
    [ "@babel/preset-env", {
      "targets": {
        "browsers": [ "last 2 versions", "ie >= 11" ]
      }
    }]
  ]
}

I'm using Babel 7 to transpile the code and the latest webpack to bundle it. When I paste this code in the console and call the function, the problem does not occur. When I paste this code in http://babeljs.io/repl and paste the resulting code in the console, it also works.

By the looks of it, this has been reported in April already, with noone commenting on the issue so far:

https://github.com/babel/babel/issues/7765

Nonetheless I've also filed it:

https://github.com/babel/babel/issues/8709

connexo
  • 53,704
  • 14
  • 91
  • 128
  • 2
    It is working for me. You need to fix `console.log(''dependancy changed)` to `console.log('dependancy changed')` – Tan Duong Sep 13 '18 at 09:23
  • 1
    "*I'm using Babel 7 to transpile the code*" - please show us the output of your transpiler, it's the only way to confirm the problem. Then, probably, go report a bug at Babel :-) – Bergi Sep 13 '18 at 10:17
  • @Bergi Added the transpiled code. – connexo Sep 13 '18 at 12:21
  • @connexo OK, thanks, it's clear now that it's Babel's fault. Since we both can't reproduce it at the online repl, what is your Babel version? – Bergi Sep 13 '18 at 12:25
  • @Bergi core 7.0.1, preset-env 7.0.0, babel-loader 8.0.2 – connexo Sep 13 '18 at 12:44
  • 1
    @connexo Hm, http://babeljs.io/repl claims to be still at 7.0.0, but I doubt that makes the difference. Still, can you try to downgrade? Also, please file a bug. – Bergi Sep 13 '18 at 12:49
  • 1
    @Bergi https://github.com/babel/babel/issues/8709 https://github.com/babel/babel/issues/7765 – connexo Sep 14 '18 at 09:09
  • @Bergi Turns out its a bug introduced in 7.0.0, see my answer below. – connexo Sep 15 '18 at 07:40

2 Answers2

2

Turns out that is really a bug introduced in Babel 7.

The reason it wasn't reproducable in the Babel REPL is that the official REPL is still on Babel 6.2.6. The 7.0 REPL is currently available here:

https://babeljs.io/repl/build/master

Babel 7 transpiles break to return "break"; if the break occurs in a case block that is wrapped in curly braces:

This works as expected with Babel 7:

switch (a) {
  case 1:
    /** some code **/
    break
  default:
}

This creates the problem:

switch (a) {
  case 1: { // <-- wrapping the case ...
    /** some code **/
    break
  } // <-- ... makes Babel go wrong
  default: {}
}

Wrapping the case in {} makes Babel transpile the break to return "break"; - which is clearly a bug on Babel side.

Temporary solution:

Just don't wrap case instructions in a new block context {} for now if you don't have to (the new block context in my original code was only for readability, so unnecessary anyway). Will update the answer when Babel has fixed that.

connexo
  • 53,704
  • 14
  • 91
  • 128
  • There are however [situations where blocks in `case` statements are necessary](https://stackoverflow.com/a/35746467/1048572)… – Bergi Sep 15 '18 at 08:41
0

The code is working just fine, the console.log("switch end") is executed as expected.

Make sure your babel version is updated.

You can check it working here: https://codepen.io/rafaelcastrocouto/pen/LJrRqj

'use strict';

function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }

function dependantControls() {
  var dependers = [].concat(_toConsumableArray(document.querySelectorAll('[data-depends-on]')));
  var _iteratorNormalCompletion = true;
  var _didIteratorError = false;
  var _iteratorError = undefined;

  try {
    var _loop = function _loop() {
      var depender = _step.value;

      var dependency = document.getElementById(depender.dataset.dependsOn);
      if (dependency) {
        var dependencyDetails = {
          prop: null,
          state: null
        };
        switch (dependency.type) {
          case 'checkbox':
            {
              dependencyDetails.prop = 'checked';
              dependencyDetails.state = false;
              break; // this break exits the current for loop iteration
            }
          case 'text':
            {
              dependencyDetails.prop = 'value';
              dependencyDetails.state = '';
              break; // this break exits the current for loop iteration
            }
          default:
            console.log('default case');
        }
        console.log("switch end"); // this gets never executed!
        depender.disabled = !dependency[dependencyDetails.prop];
        dependency.addEventListener('change', function () {
          console.log('dependancy changed');
          depender.disabled = dependency[dependencyDetails.prop] === dependencyDetails.state;
        });
      }
    };

    for (var _iterator = dependers[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
      _loop();
    }
  } catch (err) {
    _didIteratorError = true;
    _iteratorError = err;
  } finally {
    try {
      if (!_iteratorNormalCompletion && _iterator.return) {
        _iterator.return();
      }
    } finally {
      if (_didIteratorError) {
        throw _iteratorError;
      }
    }
  }

  console.log('for...end');
}

document.addEventListener('DOMContentLoaded', dependantControls);
<div>
  <input type="checkbox" id="fooBar" />
  <label for="fooBar">dependency</label>
</div>
<hr />
<div>
  <label for="fooBaz">depender</label>
  <input type="text" id="fooBaz" data-depends-on="fooBar" value="depender" />
</div>
rafaelcastrocouto
  • 11,781
  • 3
  • 38
  • 63
  • Can you elaborate in detail on your configuration? I have added the function `_toConsumableArray(arr)` function in my example with the transpiled code. You'll see it fails just as I asked. – connexo Sep 13 '18 at 14:40
  • Where I have `var dependers = _toConsumableArray(document.querySelectorAll('[data-depends-on]'));` you have `var dependers = [].concat(_toConsumableArray(document.querySelectorAll('[data-depends-on]')));` – connexo Sep 13 '18 at 14:44
  • I did not generate the code myself ... I just copied the transpiled code from codepen. The thing you should pay attention is that it works in multiple platforms as you can see in the comments. – rafaelcastrocouto Sep 13 '18 at 19:48
  • 1
    In your fiddle `break` is transpiled to `break;` and in mine to `return "break";` - which is causing the issue. – connexo Sep 14 '18 at 09:16
  • Well spotted, must have been hard to find it out. You should consider using loop labels, it will probably avoid this issue. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/label – rafaelcastrocouto Sep 14 '18 at 10:43
  • 1
    Turns out its a bug introduced in 7.0.0, see my answer. – connexo Sep 15 '18 at 07:49