diff --git a/src/browser/ui/dom/components/ReactDOMInput.js b/src/browser/ui/dom/components/ReactDOMInput.js
index 1fd4351251617..b86cf779530ef 100644
--- a/src/browser/ui/dom/components/ReactDOMInput.js
+++ b/src/browser/ui/dom/components/ReactDOMInput.js
@@ -84,7 +84,21 @@ var ReactDOMInput = ReactCompositeComponent.createClass({
var checked = LinkedValueUtils.getChecked(this);
props.checked = checked != null ? checked : this.state.initialChecked;
- props.onChange = this._handleChange;
+ // Skip attaching top-level event handlers to an uncontrolled input with no
+ // onChange handler -- in the case of radio buttons, we still need the
+ // handler even if this radio button is "uncontrolled" because clicking A in
+ //
+ //
+ // shouldn't uncheck B. (If we had a "weak listen" operation that put the
+ // listener but didn't attach the handlers to the DOM, we could use that
+ // instead for uncontrolled radios.)
+ if (value != null || checked != null ||
+ LinkedValueUtils.getOnChange(this) != null ||
+ this.props.type === "radio") {
+ props.onChange = this._handleChange;
+ } else {
+ props.onChange = null;
+ }
return input(props, this.props.children);
},
diff --git a/src/browser/ui/dom/components/ReactDOMSelect.js b/src/browser/ui/dom/components/ReactDOMSelect.js
index 3b965c60bad48..2dd29b9d87550 100644
--- a/src/browser/ui/dom/components/ReactDOMSelect.js
+++ b/src/browser/ui/dom/components/ReactDOMSelect.js
@@ -30,11 +30,10 @@ var merge = require('merge');
// Store a reference to the