reactjsjsx

Why two divs are being rendered


I’m creating a custom cursor by dynamically appending a div element in the useEffect hook when the component mounts and is set to follow the mouse using event listeners. However, two divs are being rendered—one properly follows the mouse, while the other stays at the top-left corner of the viewport.

import { useRef, useEffect, useState } from "react"

function Pcanvas({width, height, pixelSize}){
    const [canvasColor, setCanvasColor] = useState("#dcdcdc")
    const [pencilSize, setPencilSize] = useState(pixelSize)
    const canvasRef = useRef(null)
    var mousePressed = false

    function mouseCursor(){
        const cursor = document.createElement("div")
        cursor.className = 'cursor'
        cursor.style.backgroundColor = 'black'
        cursor.style.width = pencilSize + 'px'
        cursor.style.height = pencilSize + 'px'
        cursor.style.position = 'absolute';
        cursor.style.display = 'block';
        cursor.style.zIndex = 999;
        cursor.style.pointerEvents = 'none';
        document.body.appendChild(cursor)
        
        console.log(cursor.getBoundingClientRect().y)
    }
    
    function handleMouseMov(event){
        const cursor = document.getElementsByClassName('cursor')[0]
        console.log(cursor.getBoundingClientRect().y)
        if(cursor){
            cursor.style.top = event.clientY-(pencilSize/2) + 'px'
            cursor.style.left = event.clientX-(pencilSize/2) + 'px'
        }
    }

    function handleMouseLeave(event){
        const cursor = document.getElementsByClassName('cursor')[0]
        if(cursor){
            cursor.style.display = 'none'
        }
    }
    
    function handleMouseEnter(event){
        const cursor = document.getElementsByClassName('cursor')[0]
        if(cursor){
            cursor.style.display = 'block'
        }
    }
    
    useEffect(() => {
        const canvas = canvasRef.current 
        mouseCursor()
        document.body.addEventListener('mouseenter', handleMouseEnter)
        document.body.addEventListener('mousemove', handleMouseMov)
        document.body.addEventListener('mouseleave', handleMouseLeave)

        return(() => {
            document.body.removeEventListener('mouseenter', handleMouseEnter)
            document.body.removeEventListener('mousemove', handleMouseMov)
            document.body.removeEventListener('mouseleave', handleMouseLeave)
        })
    }, [pencilSize])

    return(
        <></>
    )
}

export default Pcanvas


Solution

  • You should avoid createElement and especially getElementsByClassName when using React.

    Let the component self render the div, then use an useRef to change any styling when needed.

    const { useRef, useEffect, useState } = React;
    
    function Pcanvas({ width, height, pixelSize = 15 }){
    
        const cursor = useRef(null)
        
        const [pencilSize, setPencilSize] = useState(pixelSize)
           
        const handleMouseEnter = (event) => cursor.current.style.display = 'block';
        const handleMouseLeave = (event) => cursor.current.style.display = 'none';
        const handleMouseMov = (event) => {
            cursor.current.style.top = event.clientY-(pencilSize/2) + 'px'
            cursor.current.style.left = event.clientX-(pencilSize/2) + 'px'
        }
        
        useEffect(() => {
            document.body.addEventListener('mouseenter', handleMouseEnter);
            document.body.addEventListener('mousemove', handleMouseMov);
            document.body.addEventListener('mouseleave', handleMouseLeave);
            return(() => {
                document.body.removeEventListener('mouseenter', handleMouseEnter);
                document.body.removeEventListener('mousemove', handleMouseMov);
                document.body.removeEventListener('mouseleave', handleMouseLeave);
            })
        }, [pencilSize])
        
        return (
            <div 
                ref={cursor} 
                className={'cursor'} 
                style={{ width: pencilSize, height: pencilSize, display: 'none' }}
            />
        );
    }
    
    ReactDOM.render(<Pcanvas />, document.getElementById("react"));
    html, body, #react { height: 100%; }
    
    .cursor {
      background: black;
      position: absolute;
      display: block;
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
    <div id="react"></div>