Skip to content

Commit 76260b5

Browse files
author
jim
committed
Started setting both props.class and props.className for DOM elements
1 parent 3a4e1db commit 76260b5

File tree

5 files changed

+122
-12
lines changed

5 files changed

+122
-12
lines changed

src/addons/getCssClassFromProps.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/**
2+
* Copyright 2013-present, Facebook, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree. An additional grant
7+
* of patent rights can be found in the PATENTS file in the same directory.
8+
*
9+
* @providesModule getCssClassFromProps
10+
*/
11+
12+
'use strict';
13+
14+
var warning = require('warning');
15+
16+
/**
17+
* Takes in an props (or an element) and returns the CSS class prop.
18+
*/
19+
function getCssClassFromProps(props) {
20+
if (__DEV__) {
21+
getCssClassFromProps.isExecuting = true;
22+
warning(
23+
props.className == null || props.class == null || props.class === props.className,
24+
'props.className and props.class should have the same values'
25+
);
26+
}
27+
var cls = props.className || props.class;
28+
if (__DEV__) {
29+
getCssClassFromProps.isExecuting = false;
30+
}
31+
return cls;
32+
}
33+
34+
getCssClassFromProps.isExecuting = false;
35+
36+
module.exports = getCssClassFromProps;

src/isomorphic/classic/element/ReactElement.js

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ var ReactCurrentOwner = require('ReactCurrentOwner');
1616
var assign = require('Object.assign');
1717
var warning = require('warning');
1818
var canDefineProperty = require('canDefineProperty');
19+
var getCssClassFromProps = require('getCssClassFromProps');
20+
var warning = require('warning');
1921

2022
// The Symbol used to tag the ReactElement type. If there is no native Symbol
2123
// nor polyfill, then a plain number is used for performance.
@@ -32,6 +34,42 @@ var RESERVED_PROPS = {
3234

3335
var specialPropKeyWarningShown, specialPropRefWarningShown;
3436

37+
function setupClassProp(type, config, props) {
38+
if (config != null) {
39+
if (config.className !== config.class) {
40+
if (config.class == null) {
41+
props.class = config.className;
42+
} else if (config.className == null) {
43+
props.className = config.class;
44+
} else {
45+
warning(
46+
false,
47+
'class and className values (`%s` and `%s` respectively) ' +
48+
'differ for element type: %s',
49+
config.class,
50+
config.className,
51+
type
52+
);
53+
}
54+
}
55+
56+
if (__DEV__ && props.className !== undefined) {
57+
var className = props.className;
58+
Object.defineProperty(props, 'className', {
59+
get: function() {
60+
warning(
61+
getCssClassFromProps.isExecuting,
62+
'Reading `className` prop directly from `%s` element is deprecated, ' +
63+
'use `getCssClassFromProps(props)` instead.',
64+
type
65+
);
66+
return className;
67+
},
68+
});
69+
}
70+
}
71+
}
72+
3573
/**
3674
* Factory method to create a new React element. This no longer adheres to
3775
* the class pattern, so do not use new to call it. Also, no instanceof check
@@ -144,6 +182,9 @@ ReactElement.createElement = function(type, config, children) {
144182
props[propName] = config[propName];
145183
}
146184
}
185+
if (typeof type === 'string') {
186+
setupClassProp(type, config, props);
187+
}
147188
}
148189

149190
// Children can be more than one argument, and those are transferred onto
@@ -269,6 +310,9 @@ ReactElement.cloneAndReplaceProps = function(oldElement, newProps) {
269310
};
270311

271312
ReactElement.cloneElement = function(element, config, children) {
313+
if (__DEV__) {
314+
getCssClassFromProps.isExecuting = true;
315+
}
272316
var propName;
273317

274318
// Original props are copied
@@ -305,6 +349,10 @@ ReactElement.cloneElement = function(element, config, children) {
305349
}
306350
}
307351

352+
if (typeof element.type === 'string') {
353+
setupClassProp(element.type, config, props);
354+
}
355+
308356
// Children can be more than one argument, and those are transferred onto
309357
// the newly allocated props object.
310358
var childrenLength = arguments.length - 2;
@@ -318,6 +366,10 @@ ReactElement.cloneElement = function(element, config, children) {
318366
props.children = childArray;
319367
}
320368

369+
if (__DEV__) {
370+
getCssClassFromProps.isExecuting = false;
371+
}
372+
321373
return ReactElement(
322374
element.type,
323375
key,

src/isomorphic/classic/element/__tests__/ReactElement-test.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
var React;
1818
var ReactDOM;
1919
var ReactTestUtils;
20+
var getCssClassFromProps;
2021

2122
describe('ReactElement', function() {
2223
var ComponentClass;
@@ -33,6 +34,7 @@ describe('ReactElement', function() {
3334
React = require('React');
3435
ReactDOM = require('ReactDOM');
3536
ReactTestUtils = require('ReactTestUtils');
37+
getCssClassFromProps = require('getCssClassFromProps');
3638
ComponentClass = React.createClass({
3739
render: function() {
3840
return React.createElement('div');
@@ -339,7 +341,7 @@ describe('ReactElement', function() {
339341
expect(function() {
340342
el.props.className = 'quack';
341343
}).toThrow();
342-
expect(el.props.className).toBe('moo');
344+
expect(getCssClassFromProps(el.props)).toBe('moo');
343345

344346
return el;
345347
},

src/renderers/dom/shared/ReactDOMComponent.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ var ReactPerf = require('ReactPerf');
3737

3838
var assign = require('Object.assign');
3939
var escapeTextContentForBrowser = require('escapeTextContentForBrowser');
40+
var getCssClassFromProps = require('getCssClassFromProps');
4041
var invariant = require('invariant');
4142
var isEventSupported = require('isEventSupported');
4243
var keyOf = require('keyOf');
@@ -55,6 +56,7 @@ var CONTENT_TYPES = {'string': true, 'number': true};
5556

5657
var CHILDREN = keyOf({children: null});
5758
var STYLE = keyOf({style: null});
59+
var CLASS = keyOf({'class': null});
5860
var HTML = keyOf({__html: null});
5961

6062
function getDeclarationErrorAddendum(internalInstance) {
@@ -604,6 +606,9 @@ ReactDOMComponent.Mixin = {
604606
* @return {string} Markup of opening tag.
605607
*/
606608
_createOpenTagMarkupAndPutListeners: function(transaction, props) {
609+
if (__DEV__) {
610+
getCssClassFromProps.isExecuting = true;
611+
}
607612
var ret = '<' + this._currentElement.type;
608613

609614
for (var propKey in props) {
@@ -645,6 +650,10 @@ ReactDOMComponent.Mixin = {
645650
}
646651
}
647652

653+
if (__DEV__) {
654+
getCssClassFromProps.isExecuting = false;
655+
}
656+
648657
// For static pages, no need to put React ID and checksum. Saves lots of
649658
// bytes.
650659
if (transaction.renderToStaticMarkup) {
@@ -822,6 +831,9 @@ ReactDOMComponent.Mixin = {
822831
* @param {?DOMElement} node
823832
*/
824833
_updateDOMProperties: function(lastProps, nextProps, transaction) {
834+
if (__DEV__) {
835+
getCssClassFromProps.isExecuting = true;
836+
}
825837
var propKey;
826838
var styleName;
827839
var styleUpdates;
@@ -831,6 +843,9 @@ ReactDOMComponent.Mixin = {
831843
lastProps[propKey] == null) {
832844
continue;
833845
}
846+
if (propKey === CLASS) {
847+
continue;
848+
}
834849
if (propKey === STYLE) {
835850
var lastStyle = this._previousStyleCopy;
836851
for (styleName in lastStyle) {
@@ -945,6 +960,9 @@ ReactDOMComponent.Mixin = {
945960
this
946961
);
947962
}
963+
if (__DEV__) {
964+
getCssClassFromProps.isExecuting = false;
965+
}
948966
},
949967

950968
/**

src/test/__tests__/ReactTestUtils-test.js

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ var React;
1515
var ReactDOM;
1616
var ReactDOMServer;
1717
var ReactTestUtils;
18+
var getCssClassFromProps;
1819

1920
describe('ReactTestUtils', function() {
2021

@@ -23,15 +24,16 @@ describe('ReactTestUtils', function() {
2324
ReactDOM = require('ReactDOM');
2425
ReactDOMServer = require('ReactDOMServer');
2526
ReactTestUtils = require('ReactTestUtils');
27+
getCssClassFromProps = require('getCssClassFromProps');
2628
});
2729

2830
it('should have shallow rendering', function() {
2931
var SomeComponent = React.createClass({
3032
render: function() {
3133
return (
3234
<div>
33-
<span className="child1" />
34-
<span className="child2" />
35+
<img src="child1" />
36+
<img src="child2" />
3537
</div>
3638
);
3739
},
@@ -42,8 +44,8 @@ describe('ReactTestUtils', function() {
4244

4345
expect(result.type).toBe('div');
4446
expect(result.props.children).toEqual([
45-
<span className="child1" />,
46-
<span className="child2" />,
47+
<img src="child1" />,
48+
<img src="child2" />,
4749
]);
4850
});
4951

@@ -135,8 +137,8 @@ describe('ReactTestUtils', function() {
135137
} else {
136138
return (
137139
<div>
138-
<span className="child1" />
139-
<span className="child2" />
140+
<img src="child1" />
141+
<img src="child2" />
140142
</div>
141143
);
142144
}
@@ -147,8 +149,8 @@ describe('ReactTestUtils', function() {
147149
var result = shallowRenderer.render(<SomeComponent />);
148150
expect(result.type).toBe('div');
149151
expect(result.props.children).toEqual([
150-
<span className="child1" />,
151-
<span className="child2" />,
152+
<img src="child1" />,
153+
<img src="child2" />,
152154
]);
153155

154156
var updatedResult = shallowRenderer.render(<SomeComponent aNew="prop" />);
@@ -159,7 +161,7 @@ describe('ReactTestUtils', function() {
159161

160162
var updatedResultCausedByClick = shallowRenderer.getRenderOutput();
161163
expect(updatedResultCausedByClick.type).toBe('a');
162-
expect(updatedResultCausedByClick.props.className).toBe('was-clicked');
164+
expect(getCssClassFromProps(updatedResultCausedByClick.props)).toBe('was-clicked');
163165
});
164166

165167
it('can access the mounted component instance', function() {
@@ -209,12 +211,12 @@ describe('ReactTestUtils', function() {
209211
shallowRenderer.render(<SimpleComponent />);
210212
var result = shallowRenderer.getRenderOutput();
211213
expect(result.type).toEqual('div');
212-
expect(result.props.className).toEqual('');
214+
expect(getCssClassFromProps(result.props)).toEqual('');
213215
result.props.onClick();
214216

215217
result = shallowRenderer.getRenderOutput();
216218
expect(result.type).toEqual('div');
217-
expect(result.props.className).toEqual('clicked');
219+
expect(getCssClassFromProps(result.props)).toEqual('clicked');
218220
});
219221

220222
it('can setState in componentWillMount when shallow rendering', function() {

0 commit comments

Comments
 (0)