Reputation: 2126
I have a component similar to this:
export function MyListComponent({results}) {
const resultsWithRefs = results.map(result => {
...result,
ref: useRef(null)
}
...
}
I installed the "rules of hooks" ESlint plugin and it now complains that I am using hooks inside of a callback. I need to "enrich" every element on the list with a ref and the list is dynamic, so I cannot really think of another way to do this?
Supposedly the reason is that you cannot guarantee the order of the hooks. But .map is not an async function and the component itself is without side-effects, so I cannot see why I should not be able to do basic iteration over a list? Any input?
Upvotes: 0
Views: 2669
Reputation: 39280
Maybe the following can work for you:
const Input = React.forwardRef((props, ref) => {
return <input type="text" ref={ref} />;
});
const List = ({ results }) => {
const [current, setCurrent] = React.useState(0);
//run this before jsx is generated when results change
const refs = React.useMemo(
() =>
results.map(() => ({
current: null,
})),
[results]
);
React.useEffect(() => refs[current].current.focus(), [
current,
refs,
]);
return (
<div>
{results.map((r, i) => (
<Input ref={refs[i]} key={r} />
))}
<select
onChange={(e) => setCurrent(Number(e.target.value))}
>
{results.map((r, i) => (
<option value={i} key={r}>
{r}
</option>
))}
</select>
</div>
);
};
const App = () => {
const [results, setResults] = React.useState([1, 2]);
return (
<div>
<button
onClick={() =>
setResults((r) => [...r, r.length + 1])
}
>
add result
</button>
<List results={results} />
</div>
);
};
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Upvotes: 0
Reputation: 11156
Ciao, you could try to use createRef
hook like:
const elementsRef = useRef(results.map(() => createRef()));
Upvotes: 2