Drop "previous style" copy from Stack to bring it inline with Fiber
We've already been warning for mutating styles so now we'll freeze them in DEV instead. This isn't as good because we don't have more specific warnings than the generic error that doesn't even fire in sloppy mode. This lets Fiber pass the same tests.
Sebastian Markbage committed
Nov 20, 2016 at 19:51 UTC
977357765b44af8ff0cfea327866861073095c12
4 files changed
+39
-125
scripts/fiber/tests-failing.txt
-2
@@ -25,8 +25,6 @@ src/renderers/art/__tests__/ReactART-test.js
25
* resolves refs before componentDidUpdate
26
27
src/renderers/dom/shared/__tests__/ReactDOMComponent-test.js
28
-* should update styles when mutating style object
29
-* should warn when mutating style
28
* should empty element when removing innerHTML
29
* should transition from innerHTML to children in nested el
30
* should warn for children on void elements
scripts/fiber/tests-passing.txt
+2
@@ -560,6 +560,8 @@ src/renderers/dom/shared/__tests__/ReactBrowserEventEmitter-test.js
560
src/renderers/dom/shared/__tests__/ReactDOMComponent-test.js
561
* should handle className
562
* should gracefully handle various style value types
563
+* should not update styles when mutating a proxy style object
564
+* should throw when mutating style objectsd
565
* should not warn for "0" as a unitless style value
566
* should warn nicely about NaN in style
567
* should update styles if initially null
src/renderers/dom/shared/__tests__/ReactDOMComponent-test.js
+33
-40
@@ -72,12 +72,30 @@ describe('ReactDOMComponent', () => {
72
expect(stubStyle.fontFamily).toEqual('');
73
});
74
75
- // TODO: (poshannessy) deprecate this pattern.
76
- it('should update styles when mutating style object', () => {
77
- // not actually used. Just to suppress the style mutation warning
78
- spyOn(console, 'error');
79
-
80
- var styles = {display: 'none', fontFamily: 'Arial', lineHeight: 1.2};
75
+ it('should not update styles when mutating a proxy style object', () => {
76
+ var styleStore = {display: 'none', fontFamily: 'Arial', lineHeight: 1.2};
77
+ // We use a proxy style object so that we can mutate it even if it is
78
+ // frozen in DEV.
79
+ var styles = {
80
+ get display() {
81
+ return styleStore.display;
82
+ },
83
+ set display(v) {
84
+ styleStore.display = v;
85
+ },
86
+ get fontFamily() {
87
+ return styleStore.fontFamily;
88
+ },
89
+ set fontFamily(v) {
90
+ styleStore.fontFamily = v;
91
+ },
92
+ get lineHeight() {
93
+ return styleStore.lineHeight;
94
+ },
95
+ set lineHeight(v) {
96
+ styleStore.lineHeight = v;
97
+ },
98
+ };
99
var container = document.createElement('div');
100
ReactDOM.render(<div style={styles} />, container);
101
@@ -88,23 +106,23 @@ describe('ReactDOMComponent', () => {
106
styles.display = 'block';
107
108
ReactDOM.render(<div style={styles} />, container);
91
- expect(stubStyle.display).toEqual('block');
109
+ expect(stubStyle.display).toEqual('none');
110
expect(stubStyle.fontFamily).toEqual('Arial');
111
expect(stubStyle.lineHeight).toEqual('1.2');
112
113
styles.fontFamily = 'Helvetica';
114
115
ReactDOM.render(<div style={styles} />, container);
98
- expect(stubStyle.display).toEqual('block');
99
- expect(stubStyle.fontFamily).toEqual('Helvetica');
116
+ expect(stubStyle.display).toEqual('none');
117
+ expect(stubStyle.fontFamily).toEqual('Arial');
118
expect(stubStyle.lineHeight).toEqual('1.2');
119
120
styles.lineHeight = 0.5;
121
122
ReactDOM.render(<div style={styles} />, container);
105
- expect(stubStyle.display).toEqual('block');
106
- expect(stubStyle.fontFamily).toEqual('Helvetica');
107
- expect(stubStyle.lineHeight).toEqual('0.5');
123
+ expect(stubStyle.display).toEqual('none');
124
+ expect(stubStyle.fontFamily).toEqual('Arial');
125
+ expect(stubStyle.lineHeight).toEqual('1.2');
126
127
ReactDOM.render(<div style={undefined} />, container);
128
expect(stubStyle.display).toBe('');
@@ -112,9 +130,7 @@ describe('ReactDOMComponent', () => {
130
expect(stubStyle.lineHeight).toBe('');
131
});
132
115
- it('should warn when mutating style', () => {
116
- spyOn(console, 'error');
117
-
133
+ it('should throw when mutating style objectsd', () => {
134
var style = {border: '1px solid black'};
135
136
class App extends React.Component {
@@ -125,31 +141,8 @@ describe('ReactDOMComponent', () => {
141
}
142
}
143
128
- var stub = ReactTestUtils.renderIntoDocument(<App />);
129
- style.position = 'absolute';
130
- stub.setState({style: style});
131
- expectDev(console.error.calls.count()).toBe(1);
132
- expectDev(console.error.calls.argsFor(0)[0]).toEqual(
133
- 'Warning: `div` was passed a style object that has previously been ' +
134
- 'mutated. Mutating `style` is deprecated. Consider cloning it ' +
135
- 'beforehand. Check the `render` of `App`. Previous style: ' +
136
- '{border: "1px solid black"}. Mutated style: ' +
137
- '{border: "1px solid black", position: "absolute"}.'
138
- );
139
-
140
- style = {background: 'red'};
141
- stub = ReactTestUtils.renderIntoDocument(<App />);
142
- style.background = 'green';
143
- stub.setState({style: {background: 'green'}});
144
- // already warned once for the same component and owner
145
- expectDev(console.error.calls.count()).toBe(1);
146
-
147
- style = {background: 'red'};
148
- var div = document.createElement('div');
149
- ReactDOM.render(<span style={style} />, div);
150
- style.background = 'blue';
151
- ReactDOM.render(<span style={style} />, div);
152
- expectDev(console.error.calls.count()).toBe(2);
144
+ ReactTestUtils.renderIntoDocument(<App />);
145
+ expectDev(() => style.position = 'absolute').toThrow();
146
});
147
148
it('should warn for unknown prop', () => {
src/renderers/dom/stack/client/ReactDOMComponent.js
+4
-83
@@ -35,7 +35,6 @@ var emptyFunction = require('emptyFunction');
35
var escapeTextContentForBrowser = require('escapeTextContentForBrowser');
36
var invariant = require('invariant');
37
var isEventSupported = require('isEventSupported');
38
-var shallowEqual = require('shallowEqual');
38
var inputValueTracking = require('inputValueTracking');
39
var validateDOMNesting = require('validateDOMNesting');
40
var warning = require('warning');
@@ -74,69 +73,6 @@ function getDeclarationErrorAddendum(internalInstance) {
73
return '';
74
}
75
77
-function friendlyStringify(obj) {
78
- if (typeof obj === 'object') {
79
- if (Array.isArray(obj)) {
80
- return '[' + obj.map(friendlyStringify).join(', ') + ']';
81
- } else {
82
- var pairs = [];
83
- for (var key in obj) {
84
- if (Object.prototype.hasOwnProperty.call(obj, key)) {
85
- var keyEscaped = /^[a-z$_][\w$_]*$/i.test(key) ?
86
- key :
87
- JSON.stringify(key);
88
- pairs.push(keyEscaped + ': ' + friendlyStringify(obj[key]));
89
- }
90
- }
91
- return '{' + pairs.join(', ') + '}';
92
- }
93
- } else if (typeof obj === 'string') {
94
- return JSON.stringify(obj);
95
- } else if (typeof obj === 'function') {
96
- return '[function object]';
97
- }
98
- // Differs from JSON.stringify in that undefined because undefined and that
99
- // inf and nan don't become null
100
- return String(obj);
101
-}
102
-
103
-var styleMutationWarning = {};
104
-
105
-function checkAndWarnForMutatedStyle(style1, style2, component) {
106
- if (style1 == null || style2 == null) {
107
- return;
108
- }
109
- if (shallowEqual(style1, style2)) {
110
- return;
111
- }
112
-
113
- var componentName = component._tag;
114
- var owner = component._currentElement._owner;
115
- var ownerName;
116
- if (owner) {
117
- ownerName = owner.getName();
118
- }
119
-
120
- var hash = ownerName + '|' + componentName;
121
-
122
- if (styleMutationWarning.hasOwnProperty(hash)) {
123
- return;
124
- }
125
-
126
- styleMutationWarning[hash] = true;
127
-
128
- warning(
129
- false,
130
- '`%s` was passed a style object that has previously been mutated. ' +
131
- 'Mutating `style` is deprecated. Consider cloning it beforehand. Check ' +
132
- 'the `render` %s. Previous style: %s. Mutated style: %s.',
133
- componentName,
134
- owner ? 'of `' + ownerName + '`' : 'using <' + componentName + '>',
135
- friendlyStringify(style1),
136
- friendlyStringify(style2)
137
- );
138
-}
139
-
76
/**
77
* @param {object} component
78
* @param {?object} props
@@ -482,8 +418,6 @@ function ReactDOMComponent(element) {
418
this._tag = tag.toLowerCase();
419
this._namespaceURI = null;
420
this._renderedChildren = null;
485
- this._previousStyle = null;
486
- this._previousStyleCopy = null;
421
this._hostNode = null;
422
this._hostParent = null;
423
this._rootNodeID = 0;
@@ -763,10 +697,8 @@ ReactDOMComponent.Mixin = {
697
if (propKey === STYLE) {
698
if (propValue) {
699
if (__DEV__) {
766
- // See `_updateDOMProperties`. style block
767
- this._previousStyle = propValue;
700
+ Object.freeze(propValue);
701
}
769
- propValue = this._previousStyleCopy = Object.assign({}, props.style);
702
}
703
propValue = CSSPropertyOperations.createMarkupForStyles(propValue, this);
704
}
@@ -1003,14 +935,13 @@ ReactDOMComponent.Mixin = {
935
continue;
936
}
937
if (propKey === STYLE) {
1006
- var lastStyle = this._previousStyleCopy;
938
+ var lastStyle = lastProps[STYLE];
939
for (styleName in lastStyle) {
940
if (lastStyle.hasOwnProperty(styleName)) {
941
styleUpdates = styleUpdates || {};
942
styleUpdates[styleName] = '';
943
}
944
}
1013
- this._previousStyleCopy = null;
945
} else if (registrationNameModules.hasOwnProperty(propKey)) {
946
// Do nothing for event names.
947
} else if (isCustomComponent(this._tag, lastProps)) {
@@ -1028,9 +959,7 @@ ReactDOMComponent.Mixin = {
959
}
960
for (propKey in nextProps) {
961
var nextProp = nextProps[propKey];
1031
- var lastProp =
1032
- propKey === STYLE ? this._previousStyleCopy :
1033
- lastProps != null ? lastProps[propKey] : undefined;
962
+ var lastProp = lastProps != null ? lastProps[propKey] : undefined;
963
if (!nextProps.hasOwnProperty(propKey) ||
964
nextProp === lastProp ||
965
nextProp == null && lastProp == null) {
@@ -1039,16 +968,8 @@ ReactDOMComponent.Mixin = {
968
if (propKey === STYLE) {
969
if (nextProp) {
970
if (__DEV__) {
1042
- checkAndWarnForMutatedStyle(
1043
- this._previousStyleCopy,
1044
- this._previousStyle,
1045
- this
1046
- );
1047
- this._previousStyle = nextProp;
971
+ Object.freeze(nextProp);
972
}
1049
- nextProp = this._previousStyleCopy = Object.assign({}, nextProp);
1050
- } else {
1051
- this._previousStyleCopy = null;
973
}
974
if (lastProp) {
975
// Unset styles on `lastProp` but not on `nextProp`.