React: creating and referencing dynamic component refs
Solution 1
The following keeps refs to each span inside of an array called this.spans
This works because each span adds itself to the refs array using the addSpanRef
method.
getAllNames
then goes through all referenced spans and grabs their textContent.
If you have sub components (instead of spans), you can then call methods on those children nodes!
var NamesList = React.createClass({
// returns an array of names
getAllNames() {
return this.spans.map((span) => span.textContent);
},
// Add to our spans refs array
addSpanRef (node) {
this.spans = [...this.spans, node];
},
render: function() {
this.spans = []; // create the spans refs array
var names = ['jack','fred','bob','carl'];
// Save a ref to "this" for the forEach loop
var thisRef = this;
names.forEach(function (name) {
spans.push(<span contentEditable={true} ref={thisRef.addSpanRef}>name</span>);
});
return (
<div>
{spans}
</div>;
);
}
});
ReactDOM.render(<NamesList></NamesList>, mountNode);
Solution 2
1. Quick and Dirty Solution: Read names through ref on container component
One way to read all names is to put a ref on the container, and read textcontent on childNode spans.
componentDidMount: function() {
var elem = this.refs.container.getDOMNode();
var nameNodes = elem.children;
var names = [];
for (var i=0; i<nameNodes.length; i++) {
names.push(nameNodes[i].textContent);
}
console.log(names);
},
You can find Working codepen of this here.
Warning: The code above is dirty: the user can change the content of the span, without react knowing about the DOM change. And that is a (very) bad idea.
2. Cleaner Solution
So you will need state: the user can change the content of the spans, and react needs to know about it.
Because you also need an array of all (new) edited names, this state needs to reside at container level.
Here is a solution with pure <EditableSpan>
components, which call a method on their parent each time a name is changed.
var EditableSpan = React.createClass({
onChange: function() {
var newContent = event.target.textContent;
this.props.onChange(this.props.index, newContent);
},
render: function() {
return <span
contentEditable={true}
onInput={this.onChange}>
{this.props.name}</span>
}
});
Please note that this cleaner solution no longer uses refs, because they are not needed: all data is known to react, so react does not need refs to read from the DOM. Instead, it reads the event.target
on every change, to update container state.
You can find full working codepen here.
Caveat: for this solution I have added quick and dirty keys
(the names). React needs keys which are unique (and NOT the index). Because the names in the list are not guaranteed to be unique, you may need another smarter solution for this (IDs or timestamps of creation would do).
joshuakcockrell
Former Software Engineer @ Robinhood, Twitter, Tesla, Microsoft, Adobe
Updated on June 04, 2022Comments
-
joshuakcockrell almost 2 years
I'm trying to keep track of an arbitrary amount of sub components.
Normally you would just reference this.refs.refName, but in my case I have an arbitrary amount of refs I need to keep track of.
Here's a concise example:
var NamesList = React.createClass({ getAllNames: function() { // Somehow return an array of names... var names = []; this.refs.????.forEach(function (span) { names.push(span.textContent); }) }, render: function() { var names = ['jack','fred','bob','carl']; var spans = []; names.forEach(function (name) { spans.push(<span contentEditable={true} ref='?????'>name</span>); }); return ( <div> {spans} </div>; ); } }); ReactDOM.render(<NamesList></NamesList>, mountNode);
If I'm approaching the problem incorrectly let me know. My desired outcome is to pass in data from a RESTful service to a React component, allow the user to edit that data, and export it again when needed. I've been unable to find an answer to this in the React refs docs.
-
joshuakcockrell about 8 yearsSorry, I'm trying to pull the names out of the spans dynamically. It looks like the changes you made are about setting spans dynamically. In pseudocode: for each span, grab the textContent and return these as an array. Is that helpful to explain what I'm asking?
-
wintvelt about 8 yearsUpdated the answer with 2 solutions. Please note that the second solution does return an array with the textContent of the spans, but without using refs. React is and should be the only owner and manager of the textContent. So it is not necessary (and source of buggy/ unmanageable code) to use refs to get the names.