@samitouri / QOS-React / commits / 977357765b

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`.