cloneWithProps()
what if you want to change the props of a child? This is my first attempt which lets you clone a child and transfer some custom props to it. There is a fundamental issue with refs here. Since the component is cloned the ref will be broken. And since we can clone multiple times, it might not make sense to support repairing refs.
Pete Hunt committed
Jan 2, 2014 at 15:41 UTC
cc005668b56028ab35353bc2d671e9fb59d208f8
4 files changed
+172
-19
src/core/ReactDOM.js
+2
@@ -50,6 +50,8 @@ function createDOMComponentClass(tag, omitClose) {
50
instance.construct.apply(instance, arguments);
51
return instance;
52
};
53
+
54
+ Constructor.ConvenienceConstructor = ConvenienceConstructor;
55
ConvenienceConstructor.componentConstructor = Constructor;
56
return ConvenienceConstructor;
57
}
src/core/ReactPropTransferer.js
+32
-19
@@ -76,6 +76,33 @@ var ReactPropTransferer = {
76
77
TransferStrategies: TransferStrategies,
78
79
+ /**
80
+ * Merge two props objects using TransferStrategies.
81
+ *
82
+ * @param {object} oldProps original props (they take precedence)
83
+ * @param {object} newProps new props to merge in
84
+ * @return {object} a new object containing both sets of props merged.
85
+ */
86
+ mergeProps: function(oldProps, newProps) {
87
+ var props = merge(oldProps);
88
+
89
+ for (var thisKey in newProps) {
90
+ if (!newProps.hasOwnProperty(thisKey)) {
91
+ continue;
92
+ }
93
+
94
+ var transferStrategy = TransferStrategies[thisKey];
95
+
96
+ if (transferStrategy) {
97
+ transferStrategy(props, thisKey, newProps[thisKey]);
98
+ } else if (!props.hasOwnProperty(thisKey)) {
99
+ props[thisKey] = newProps[thisKey];
100
+ }
101
+ }
102
+
103
+ return props;
104
+ },
105
+
106
/**
107
* @lends {ReactPropTransferer.prototype}
108
*/
@@ -104,29 +131,15 @@ var ReactPropTransferer = {
131
component.constructor.displayName
132
);
133
107
- var props = {};
108
- for (var thatKey in component.props) {
109
- if (component.props.hasOwnProperty(thatKey)) {
110
- props[thatKey] = component.props[thatKey];
111
- }
112
- }
113
- for (var thisKey in this.props) {
114
- if (!this.props.hasOwnProperty(thisKey)) {
115
- continue;
116
- }
117
- var transferStrategy = TransferStrategies[thisKey];
118
- if (transferStrategy) {
119
- transferStrategy(props, thisKey, this.props[thisKey]);
120
- } else if (!props.hasOwnProperty(thisKey)) {
121
- props[thisKey] = this.props[thisKey];
122
- }
123
- }
124
- component.props = props;
134
+ component.props = ReactPropTransferer.mergeProps(
135
+ component.props,
136
+ this.props
137
+ );
138
+
139
return component;
140
}
141
142
}
129
-
143
};
144
145
module.exports = ReactPropTransferer;
src/utils/__tests__/cloneWithProps-test.js
new
+89
@@ -0,0 +1,89 @@
1
+/**
2
+ * Copyright 2013 Facebook, Inc.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ *
16
+ * @emails react-core
17
+ * @jsx React.DOM
18
+ */
19
+
20
+"use strict";
21
+
22
+require('mock-modules').dontMock('cloneWithProps');
23
+
24
+var mocks = require('mocks');
25
+
26
+var cloneWithProps = require('cloneWithProps');
27
+
28
+var React;
29
+var ReactTestUtils;
30
+
31
+var onlyChild;
32
+
33
+describe('cloneWithProps', function() {
34
+
35
+ beforeEach(function() {
36
+ React = require('React');
37
+ ReactTestUtils = require('ReactTestUtils');
38
+ onlyChild = require('onlyChild');
39
+ });
40
+
41
+ it('should clone an object with new props', function() {
42
+ var Grandparent = React.createClass({
43
+ render: function() {
44
+ return <Parent><div className="child" /></Parent>;
45
+ }
46
+ });
47
+ var Parent = React.createClass({
48
+ render: function() {
49
+ return (
50
+ <div className="parent">
51
+ {cloneWithProps(onlyChild(this.props.children), {className: 'xyz'})}
52
+ </div>
53
+ );
54
+ }
55
+ });
56
+ var component = ReactTestUtils.renderIntoDocument(<Grandparent />);
57
+ expect(component.getDOMNode().childNodes[0].className)
58
+ .toBe('child xyz');
59
+ });
60
+
61
+ it('should warn when cloning with refs', function() {
62
+ var Grandparent = React.createClass({
63
+ render: function() {
64
+ return <Parent><div ref="yolo" /></Parent>;
65
+ }
66
+ });
67
+ var Parent = React.createClass({
68
+ render: function() {
69
+ return (
70
+ <div>
71
+ {cloneWithProps(onlyChild(this.props.children), {className: 'xyz'})}
72
+ </div>
73
+ );
74
+ }
75
+ });
76
+
77
+ var _warn = console.warn;
78
+
79
+ try {
80
+ console.warn = mocks.getMockFunction();
81
+
82
+ var component = ReactTestUtils.renderIntoDocument(<Grandparent />);
83
+ expect(component.refs).toBe(undefined);
84
+ expect(console.warn.mock.calls.length).toBe(1);
85
+ } finally {
86
+ console.warn = _warn;
87
+ }
88
+ });
89
+});
src/utils/cloneWithProps.js
new
+49
@@ -0,0 +1,49 @@
1
+/**
2
+ * Copyright 2013 Facebook, Inc.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ *
16
+ * @typechecks
17
+ * @providesModule cloneWithProps
18
+ */
19
+
20
+"use strict";
21
+
22
+var ReactPropTransferer = require('ReactPropTransferer');
23
+
24
+/**
25
+ * Sometimes you want to change the props of a child passed to you. Usually
26
+ * this is to add a CSS class.
27
+ *
28
+ * @param {object} child child component you'd like to clone
29
+ * @param {object} props props you'd like to modify. They will be merged
30
+ * as if you used `transferPropsTo()`.
31
+ * @return {object} a clone of child with props merged in.
32
+ */
33
+function cloneWithProps(child, props) {
34
+ if (__DEV__) {
35
+ if (child.props.ref) {
36
+ console.warn(
37
+ 'You are calling cloneWithProps() on a child with a ref. This is ' +
38
+ 'dangerous because you\'re creating a new child which will not be ' +
39
+ 'added as a ref to its parent.'
40
+ );
41
+ }
42
+ }
43
+
44
+ return child.constructor.ConvenienceConstructor(
45
+ ReactPropTransferer.mergeProps(child.props, props)
46
+ );
47
+}
48
+
49
+module.exports = cloneWithProps;