I have been testing the possible limitations/dangers of using React.cloneElement()
to extend a ponent's children
. One possible danger I've identified is the possible overwriting of props such as ref
and key
.
However, as per React's 0.13 release candidate (back in 2015):
However, unlike JSX and cloneWithProps, it also preserves refs. This means that if you get a child with a ref on it, you won't accidentally steal it from your ancestor. You will get the same ref attached to your new element.
[...]
Note:
React.cloneElement(child, { ref: 'newRef' })
DOES override the ref so it is still not possible for two parents to have a ref to the same child, unless you use callback-refs.
I have written a small React application that clones children ponents pushed through, testing for the validity of refs at two levels:
class ChildComponent extends React.Component{
constructor(props){
super(props);
this.onClick = this.onClick.bind(this);
this.extendsChildren = this.extendChildren(this);
}
onClick(e) {
e.preventDefault();
try{
alert(this._input.value);
}catch(e){
alert('ref broken :(');
}
}
extendChildren(){
return React.Children.map(this.props.children, child => {
return React.cloneElement(
child,
{
ref: ref => this._input = ref
}
);
});
}
render() {
return(
<div>
<button onClick={this.onClick}>
ChildComponent ref check
</button>
{this.extendChildren()}
</div>
);
}
}
class AncestorComponent extends React.Component{
constructor(props){
super(props);
this.onClick = this.onClick.bind(this);
}
onClick(e) {
e.preventDefault();
try{
alert(this._input.value);
}catch(e){
alert('ref broken :(');
}
}
render() {
return (
<div>
<p>
The expected behaviour is that I should be able to click on both Application and ChildComponent check buttons and have a reference to the input (poping an alert with the input's value).
</p>
<button onClick={this.onClick}>
Ancestor ref check
</button>
<ChildComponent>
<input ref={ref => this._input = ref} defaultValue="Hello World"/>
</ChildComponent>
</div>
);
}
}
However, cloningElements inside my ChildComponent overwrites the AncestorComponent's ref
prop from the input field, where I would expect that ref
prop to be preserved, alongside the new ref
I defined as part of the React.cloneElement
.
You can test this by running the CodePen.
Is there anything I'm doing wrong, or has this feature been dropped since?
I have been testing the possible limitations/dangers of using React.cloneElement()
to extend a ponent's children
. One possible danger I've identified is the possible overwriting of props such as ref
and key
.
However, as per React's 0.13 release candidate (back in 2015):
However, unlike JSX and cloneWithProps, it also preserves refs. This means that if you get a child with a ref on it, you won't accidentally steal it from your ancestor. You will get the same ref attached to your new element.
[...]
Note:
React.cloneElement(child, { ref: 'newRef' })
DOES override the ref so it is still not possible for two parents to have a ref to the same child, unless you use callback-refs.
I have written a small React application that clones children ponents pushed through, testing for the validity of refs at two levels:
class ChildComponent extends React.Component{
constructor(props){
super(props);
this.onClick = this.onClick.bind(this);
this.extendsChildren = this.extendChildren(this);
}
onClick(e) {
e.preventDefault();
try{
alert(this._input.value);
}catch(e){
alert('ref broken :(');
}
}
extendChildren(){
return React.Children.map(this.props.children, child => {
return React.cloneElement(
child,
{
ref: ref => this._input = ref
}
);
});
}
render() {
return(
<div>
<button onClick={this.onClick}>
ChildComponent ref check
</button>
{this.extendChildren()}
</div>
);
}
}
class AncestorComponent extends React.Component{
constructor(props){
super(props);
this.onClick = this.onClick.bind(this);
}
onClick(e) {
e.preventDefault();
try{
alert(this._input.value);
}catch(e){
alert('ref broken :(');
}
}
render() {
return (
<div>
<p>
The expected behaviour is that I should be able to click on both Application and ChildComponent check buttons and have a reference to the input (poping an alert with the input's value).
</p>
<button onClick={this.onClick}>
Ancestor ref check
</button>
<ChildComponent>
<input ref={ref => this._input = ref} defaultValue="Hello World"/>
</ChildComponent>
</div>
);
}
}
However, cloningElements inside my ChildComponent overwrites the AncestorComponent's ref
prop from the input field, where I would expect that ref
prop to be preserved, alongside the new ref
I defined as part of the React.cloneElement
.
You can test this by running the CodePen.
Is there anything I'm doing wrong, or has this feature been dropped since?
Share Improve this question edited Jun 20, 2020 at 9:12 CommunityBot 11 silver badge asked Jan 26, 2017 at 15:15 PrusprusPrusprus 8,0659 gold badges43 silver badges58 bronze badges1 Answer
Reset to default 6As per Dan Abramov's response, overwriting the reference, even with a callback, is still going to overwrite the reference. You'll need to call the current reference as part of the callback declaration:
return React.Children.map(this.props.children, child =>
React.cloneElement(child, {
ref(node) {
// Keep your own reference
this._input = node;
// Call the original ref, if any
const {ref} = child;
if (typeof ref === 'function') {
ref(node);
}
}
)
);