4

I'm converting my jQuery based project to react and run into an issue with refs in a functional component: I have a huge wordlist and each word should be accessed via their own ref so that I can change individual classNames, depending on what the user is typing (I can do it with a huge state object, but the performance suffers).

If I try to access the classlist of a ref, its undefined. Now I'm not sure if classList is generally not available in functional components or if the refs aren't properly initialized:

const wordsRef = useRef([...Array(1000)].map(() => createRef()));

...

const displayWords = words.map((word, index) => (
    <React.Fragment key={index}>
      <span
        ref={wordsRef.current[index]}
      >
        {word}
      </span>{' '}
    </React.Fragment>
  ));

...

useEffect(() => {
  wordsRef.current[0].classList.add('highlight');
});

...

return (
    <div className={classes.root}>
      {displayWords}
    </div>
  );

Error: Cannot read property 'add' of undefined.

I was only able to find examples with classList, but this is propably not the way to add/remove classes in a functional component?

skyboyer
  • 22,209
  • 7
  • 57
  • 64
Christian Strang
  • 8,470
  • 5
  • 42
  • 53
  • Any producible example? what does `createRef` stands for? [How to create a Minimal, Reproducible Example](https://stackoverflow.com/help/minimal-reproducible-example), are you trying to use React.createRef? – Dennis Vash Dec 19 '19 at 14:24

2 Answers2

6

The code almost works fine, you assigned a reference with .current property, just change it to:

wordsRef.current[0].current.classList

But you should approach it in other way:

ref={el => (wordsRef.current = [...wordsRef.current, el])
import React, { useRef, useEffect } from 'react';
import ReactDOM from 'react-dom';

const words = ['Many', 'Words'];

const App = () => {
  const wordsRef = useRef([]);

  const displayWords = words.map((word, i) => (
    <React.Fragment key={i}>
      <span ref={el => (wordsRef.current = [...wordsRef.current, el])}>
        {word}
      </span>
    </React.Fragment>
  ));

  useEffect(() => {
    console.log(wordsRef);
    console.log(wordsRef.current[0].classList);
  });

  return <div>{displayWords}</div>;
};

ReactDOM.render(<App />, document.getElementById('root'));

Edit fervent-nobel-0fbof

Dennis Vash
  • 50,196
  • 9
  • 100
  • 118
  • still results in "Uncaught TypeError: Cannot read property 'classList' of undefined ". Any other way to add/remove the class of an element via ref? – Christian Strang Dec 19 '19 at 14:36
  • You have a sandbox example, check the logs, it defined – Dennis Vash Dec 19 '19 at 14:37
  • Thank you for your help! I assume its related to my use of Material UI and/or nextjs, will dig a bit deeper and accept your answer. – Christian Strang Dec 19 '19 at 14:41
  • In my case it was that useEffect was called before the refs were available. I now check if the refs array has at least a length of 1 to proceed. Though classList ist still not usable, however, this is probably related to "makeStyles". – Christian Strang Dec 19 '19 at 14:55
  • 2
    took me a while, but now it comes all together: `wordsRef.current[0].classList.add(classes.highlight);` – Christian Strang Dec 19 '19 at 15:04
  • In my case if the map returns span or div it works, but if I map and try to return an imported component the ref array remains empty. – Alessandro Sassi Jun 21 '21 at 15:56
1

Try declaring wordsRef as an empty array and when you assign a new ref in your map function use ref param to push a new ref to your array via spread operator.

const wordsRef = useRef([]);

const displayWords = words.map((word, index) => (
    <React.Fragment key={index}>
      <span
        ref={ref => (wordsRef.current = [...wordsRef.current, ref])}
      >
        {word}
      </span>{' '}
    </React.Fragment>
  ));