loading...
Cover image for  Using Ref in React to prevent unnecessary rerendering.

Using Ref in React to prevent unnecessary rerendering.

acsreedharreddy profile image A C SREEDHAR REDDY ・2 min read

There is a video player(used videojs library) that would fire an event timeupdate
for every 50-250ms with the currentTime as a parameter. My requirement was to listen to that event and at a particular given time I had to call a function reached().

So I started with this.

useEffect(() => {
  player.on("timeupdate", (currentTime) => {
    if (currentTime === props.time) {
      reached();
    }
  });
  return;
}, [props.time]);
Enter fullscreen mode Exit fullscreen mode

The issue here is currentTime would be a float. props.time would be an int.

eg: props.time would be 3
Player would emit timeupdate at 2.95, 3.15, 3.85, 4.1 ...
props.time(3) would never be equal to these. So reached would not be called when the player's time is 3 seconds.


So to fix this I used Math.round which would take a float and round it to an int.

useEffect(() => {
  player.on("timeupdate", (currentTime) => {
    if (Math.round(currentTime) === props.time) {
      reached();
    }
  });
  return;
}, [props.time]);

Enter fullscreen mode Exit fullscreen mode

Now, reached could be called more than once in a second.

props.time is 3. If timeupdate event is triggered at 3.05 and 3.25 both events would call reached as both 3.05 and 3.25 would be rounded to 3.


To fix this I thought a state could be maintained in the component and the state would be updated in the timeupdate event only when the Math.round(currentTime) is not same as the current state. And there could be an effect which could listen to this state change and call reached.

const [seconds, setSeconds] = useState(0);

useEffect(() => {
  if (seconds === props.time) {
    reached();
  }
}, [seconds]);

useEffect(() => {
  player.on("timeupdate", (currentTime) => {
    setSeconds(Math.round(currentTime));
  });
  return;
}, []);
Enter fullscreen mode Exit fullscreen mode

React renders for every one second.
This could create a problem when your component is heavy and takes more time to render.


To prevent this I could have memoed heavy components. But then I thought useRef would be more helpful here. Because we do not require a rerender as the view does not depend on seconds state.

I moved seconds from state to ref.

const secondsRef = useRef(0);

useEffect(() => {
  player.on("timeupdate", (currentTime) => {
    if (Math.round(currentTime) != secondsRef.current) {
      secondsRef.current = Math.round(currentTime);
      if (secondsRef.current === props.time) {
        reached();
      }
    }
  });
}, []);
Enter fullscreen mode Exit fullscreen mode

Now this solves the above issues.

  1. The reached function would be called when currentTime matches.
  2. It would be called only once in a second.
  3. Since we used ref it would not create a rerender on every second.

Discussion

pic
Editor guide