Search code examples
javascriptreactjscomponentsgatsby

"Cannot read properties of null (reading 'appendChild')" error in gatsby shows only on site refresh


I am using gatsby develop and when i enter this code for one of my components it runs fin and creates the divs however when i refresh the page it pressents the error 'Cannot read properties of null (reading 'appendChild')' for the line 'container.appendChild(circle);'. Why dose this only apper when I refresh the page and how do i fix this?

const Fun = () => {
    const container = document.getElementById('container');
    let row = 15;
    let col = 15;

    for(let i = 0; i < col; i++){
        for(let j = 0; j < row; j++){
            const circle = document.createElement('div');
            container.appendChild(circle);
        }
    }

    return (
        <div className={funWrapper}>
            <div id={'container'} className={funContainer}/>
        </div>
    )
}

Solution

  • You are creating and manipulating a virtual DOM (vDOM) with React (hence with Gatsby) while your code is pointing and manipulating the real DOM (because of the document use).

    React is not aware of changes outside its scope and vice versa, so it will never know about changes in the real DOM like the one you are doing.

    In your case, because of that, the container may or may not exist at the moment you are trying to request it (because of the refresh).

    You can take advantage of the useRef hook to create references over elements in the virtual DOM to create exactly the same behavior inside React's scope.

    const Fun = () => {
      const container = useRef(null);
    
        useEffect(()=>{
          container.current.appendChild('<div>a circle</div>')
        }, [])
    
       
        return (
            <div className={funWrapper}>
                <div id={'container'} ref={container} className={funContainer}/>
            </div>
        )
    }
    

    The idea is to wait for React's lifecycle until the DOM tree is loaded, that's what the useEffect with empty deps ([]) does. You can try adding your previous inside it but you will be breaking the vDOM-DOM scope that I mentioned before despite it may work.

    As you can see, the container.current contains the reference of your <div> and keeps exactly the same information than document.getElementById.