javascriptreactjstypescriptwebrendering

React component keeps re-rendering whenever screen is minimized or another tab is opened


I have a component in my portfolio website that creates a diagonally moving circle every couple of seconds. The problem is that when I minimize the screen or change the tab and come back after a minute, many circles will be created at the same time and the screen gets filled with them. I am not sure why this happens.

You can see the effect live in my website: https://khaledalhariri.com

Currently I have it set to have 6 circles at most in the screen. So when I minimize the window and open it after a minute, 6 circles are created at the same time which is an issue.. if I didnt set that limit of 6 circles then countless circles would have been created at the same time.

Code:

import { useEffect, useRef } from 'react';
import './banner-skills.component.css';
import SKILLS_DATA, { Skill } from '../../data/skills';

const BannerSkills = () => {
  const CREATE_CIRCLE_MS = 4500;
  const CREATE_CIRCLE_MS_MOBILE = 7000;
  const ARRAY_OF_SKILLS: Skill[] = [];
  const isFirstRender = useRef(true);

  useEffect(() => {
    const skillsContainer = document.getElementsByClassName('banner-skills-container')[0];
    let minScreenSize = window.screen.width * 0.08;
    let maxScreenSize = window.screen.width * 0.85;
    let maxCircles = 6;
    let prevInitPos = Math.floor(Math.random() * maxScreenSize) - minScreenSize;

    const createDiagonalCircle = () => {
      if(window.screen.width <= 500) {
        minScreenSize = window.screen.width * 0.8;
        maxCircles = 4;
      }
      else {
        minScreenSize = window.screen.width * 0.08;
        maxCircles = 6;
      }

      if(ARRAY_OF_SKILLS.length >= maxCircles) {
        return;
      }
      
      const circle = document.createElement('div');
      circle.classList.add('skill-circle');
      skillsContainer.appendChild(circle);
      const logo = document.createElement('img');
      let randomSkill = SKILLS_DATA[Math.floor(Math.random() * SKILLS_DATA.length)];
      
      while(ARRAY_OF_SKILLS.indexOf(randomSkill) >= 0) {
        randomSkill = SKILLS_DATA[Math.floor(Math.random() * SKILLS_DATA.length)];
      }

      logo.src = randomSkill.img;
      ARRAY_OF_SKILLS.push(randomSkill);
      circle.appendChild(logo);

      let size = Math.floor(Math.random() * 70) + 70;
      circle.style.width = size + 'px';
      circle.style.height = size + 'px';

      let posTop = -150;
      let posLeft = Math.floor(Math.random() * maxScreenSize) - minScreenSize;

      while(Math.abs(posLeft - prevInitPos) < (window.screen.width * 0.13)) {
        posLeft = Math.floor(Math.random() * maxScreenSize) - minScreenSize;
      }
      
      prevInitPos = posLeft;
      let moveCirclesInterval = setInterval(frame, size - (size * 0.65));
      
      function frame() {
        if (posTop === 500) {
          skillsContainer.removeChild(circle);
          ARRAY_OF_SKILLS.splice(ARRAY_OF_SKILLS.indexOf(randomSkill), 1);
          clearInterval(moveCirclesInterval);
        } else {
          posTop++;
          posLeft++;
          circle.style.top = posTop + 'px';
          circle.style.left = posLeft + 'px';
          circle.style.visibility = 'visible';
        }
      }
      isFirstRender.current = false;
    }

    if(isFirstRender.current === true) {
      createDiagonalCircle();
      if(window.screen.width <= 500) {
        let spawnCirclesInterval = setInterval(createDiagonalCircle, CREATE_CIRCLE_MS_MOBILE);
        return () => clearInterval(spawnCirclesInterval); 
      }
      else {
        let spawnCirclesInterval = setInterval(createDiagonalCircle, CREATE_CIRCLE_MS);
        return () => clearInterval(spawnCirclesInterval); 
      } 
    }
  }, [])

  return (
    <div className="banner-skills-container"></div>
  )
}

export default BannerSkills

The circles are created in the useEffect and I have tried to find out if the page is being re-redenderd or not to stop it but I didn't find anything to work.


Solution

  • You don't cleare the interval of the setInterval function when the user navigates away from the tab or minimizes the screen. createDiagonalCircle continues to run in the background and creates circles even when the user is not actively viewing the page.

    To fix this issue, you can use the window.blur and window.focus events to stop and restart the interval respectively. Here's an example implementation:

    useEffect(() => {
      const skillsContainer = document.getElementsByClassName('banner-skills-container')[0];
      let minScreenSize = window.screen.width * 0.08;
      let maxScreenSize = window.screen.width * 0.85;
      let maxCircles = 6;
      let prevInitPos = Math.floor(Math.random() * maxScreenSize) - minScreenSize;
      let intervalId: any;
    
      const createDiagonalCircle = () => {
        // ... rest of the code
      };
    
      const handleVisibilityChange = () => {
        if (document.hidden) {
          clearInterval(intervalId);
        } else {
          intervalId = setInterval(createDiagonalCircle, window.screen.width <= 500 ? CREATE_CIRCLE_MS_MOBILE : CREATE_CIRCLE_MS);
        }
      };
    
      if (window.screen.width <= 500) {
        minScreenSize = window.screen.width * 0.8;
        maxCircles = 4;
      } else {
        minScreenSize = window.screen.width * 0.08;
        maxCircles = 6;
      }
    
      window.addEventListener('blur', () => clearInterval(intervalId));
      window.addEventListener('focus', () => intervalId = setInterval(createDiagonalCircle, window.screen.width <= 500 ? CREATE_CIRCLE_MS_MOBILE : CREATE_CIRCLE_MS));
      document.addEventListener('visibilitychange', handleVisibilityChange);
    
      intervalId = setInterval(createDiagonalCircle, window.screen.width <= 500 ? CREATE_CIRCLE_MS_MOBILE : CREATE_CIRCLE_MS);
    
      return () => {
        clearInterval(intervalId);
        window.removeEventListener('blur', () => clearInterval(intervalId));
        window.removeEventListener('focus', () => intervalId = setInterval(createDiagonalCircle, window.screen.width <= 500 ? CREATE_CIRCLE_MS_MOBILE : CREATE_CIRCLE_MS));
        document.removeEventListener('visibilitychange', handleVisibilityChange);
      }
    }, [])