You can use callback refs to generate and store the dynamic ref of each input in an array. Now you can refer to them using the index of the ref:
const Hello = React.forwardRef((props, ref) => <input ref={ref} />);
class Button extends React.Component {
onClick = () => this.props.onClick(this.props.id);
render() {
return (
<button onClick={this.onClick}>{this.props.children}</button>
);
}
}
class TestRef extends React.Component {
state = {
data: [
{
name: "abc"
},
{ name: "def" }
]
};
inputRefs = [];
setRef = (ref) => {
this.inputRefs.push(ref);
};
focusInput = (id) => this.inputRefs[id].focus();
render() {
return (
<div>
{this.state.data.map(({ name }) => (
<Hello
placeholder={name}
ref={this.setRef}
key={name} />
))}
<Button onClick={this.focusInput} id={0}>focus input 1</Button>
<Button onClick={this.focusInput} id={1}>focus input 2</Button>
</div>
);
}
}
ReactDOM.render(<TestRef />, document.getElementById("root"));
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div id="root"></div>
If the list wouldn’t be static, and items may be removed/replaced, you should probably use a WeakMap to hold the refs, or any other method of adding the ref by a constant id. You should also check before using the ref, because it might not exist:
const Hello = React.forwardRef((props, ref) => <input ref={ref} />);
class Button extends React.Component {
onClick = () => this.props.onClick(this.props.id);
render() {
return (
<button onClick={this.onClick}>{this.props.children}</button>
);
}
}
class TestRef extends React.Component {
state = {
data: [{ name: "abc" }, { name: "def" }, { name: "ghi" }]
};
componentDidMount() {
setTimeout(() => {
this.setState(({ data }) => ({
data: data.slice(0, -1)
}))
}, 3000);
}
inputRefs = new WeakMap;
setRef = (id) => (ref) => {
this.inputRefs.set(id, ref);
};
focusInput = (id) => {
const input = this.inputRefs.get(id);
if(input) input.focus(); // use only if the ref exists - use optional chaining ?. if possible instead
}
render() {
const { data } = this.state;
return (
<div>
{data.map(o => (
<Hello
placeholder={o.name}
ref={this.setRef(o)}
key={o.name} />
))}
<br />
{data.map((o, i) => (
<Button onClick={this.focusInput} id={o} key={o.name}>focus input {i + 1}</Button>
))}
</div>
);
}
}
ReactDOM.render(<TestRef />, document.getElementById("root"));
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div id="root"></div>