zkwsk
zkwsk

Reputation: 2126

Dynamically creating Refs for a list of elements in React with hooks

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

Answers (2)

HMR
HMR

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

Giovanni Esposito
Giovanni Esposito

Reputation: 11156

Ciao, you could try to use createRef hook like:

const elementsRef = useRef(results.map(() => createRef()));

Upvotes: 2

Related Questions