Home > Enterprise >  Threejs Reactjs : TypeError: Cannot read properties of null (reading 'addEventListener')
Threejs Reactjs : TypeError: Cannot read properties of null (reading 'addEventListener')

Time:12-02

I follow this tutorial : https://youtu.be/V8GnInBUMLo but... when I finish I got this error

TypeError: Cannot read properties of null (reading 'addEventListener')

I try to had if(element) inside my addeventlisters function but I got a new error:

TypeError: Cannot read properties of null (reading 'appendChild')

I think this error comes from my importation of ImageDistortion.js in Hero.js

ImageDistortion.js

import * as THREE from 'three';
import imageOne from '../img/1.jpg';
import imageTwo from '../img/3.jpg';
import vertex from './shaders/vertex.glsl';
import fragment from './shaders/fragment.glsl';


function lerp(start, end, t){
    return start * ( 1 - t )   end * t;
}


let targetX = 0;
let targetY = 0;

const textureOne = new THREE.TextureLoader().load(imageOne);
const textureTwo = new THREE.TextureLoader().load(imageTwo);


class WebGL{
    constructor(){
        this.container = document.querySelector('.hero');
        this.links = [...document.querySelectorAll('.three--anim')];
        this.scene = new THREE.Scene();
        this.perspective = 1000;
        this.sizes = new THREE.Vector2(0,0);
        this.offset = new THREE.Vector2(0,0); // Positions of mesh on screen. Will be updated below.
        this.uniforms = {
            uTexture: {value: new THREE.TextureLoader().load(imageTwo)},
            uAlpha: {value: 0.0},
            uOffset: {value: new THREE.Vector2(0.0, 0.0)}
        }
        this.links.forEach((link, idx) => {
            link.addEventListener('mouseenter', () => {
                
                switch(idx){
                    case 0:
                        
                        this.uniforms.uTexture.value = textureOne;
                        break;
                    case 1:
                        this.uniforms.uTexture.value = textureTwo;
                        break;
                }
            })

            link.addEventListener('mouseleave', () => {
                this.uniforms.uAlpha.value = lerp(this.uniforms.uAlpha.value, 0.0, 0.1);
            });
        })
        this.addEventListeners(document.querySelector('h1'));
        this.setUpCamera();
        this.onMouseMove();
        this.createMesh();
        this.render()
        
    }

    get viewport(){
        let width = window.innerWidth;
        let height = window.innerHeight;
        let aspectRatio = width / height;

        return{
            width, 
            height, 
            aspectRatio
        }
    }

    addEventListeners(element){
        element.addEventListener('mouseenter', () => {
            this.linkHovered = true;
        })
        element.addEventListener('mouseleave', () => {
            this.linkHovered = false;
        })
    }

    setUpCamera(){
        window.addEventListener('resize', this.onWindowResize.bind(this))
        
        let fov = (180 * (2 * Math.atan(this.viewport.height / 2 / this.perspective))) / Math.PI;
        this.camera = new THREE.PerspectiveCamera(fov, this.viewport.aspectRatio, 0.1, 1000);
        this.camera.position.set(0, 0 , this.perspective);

        this.renderer = new THREE.WebGL1Renderer({antialias: true,alpha: true });
        this.renderer.setSize(this.viewport.width, this.viewport.height);
        this.renderer.setPixelRatio(window.devicePixelRatio);
        this.container.appendChild(this.renderer.domElement)
    }

    createMesh(){
        this.geometry = new THREE.PlaneGeometry(1,1,20,20);
        this.material = new THREE.ShaderMaterial({
            uniforms: this.uniforms,
            vertexShader: vertex,
            fragmentShader: fragment,
            transparent: true,
            // wireframe: true,
            // side: THREE.DoubleSide
        })
        this.mesh = new THREE.Mesh(this.geometry, this.material);
        this.sizes.set(250, 350, 1);
        this.mesh.scale.set(this.sizes.x, this.sizes.y, 1);

        this.mesh.position.set(this.offset.x, this.offset.y, 0);
        
        this.scene.add(this.mesh);
    }
    onWindowResize(){
       
        this.camera.aspect = this.viewport.aspectRatio;
        this.camera.fov = (180 * (2 * Math.atan(this.viewport.height / 2 / this.perspective))) / Math.PI;
        this.renderer.setSize(this.viewport.width, this.viewport.height);   
        this.camera.updateProjectionMatrix();
    }

    onMouseMove(){
        window.addEventListener('mousemove', (e) => {
            targetX = e.clientX;
            targetY = e.clientY;
        })
    }

    render(){
        this.offset.x = lerp(this.offset.x, targetX, 0.1);
        this.offset.y = lerp(this.offset.y, targetY, 0.1);
        this.uniforms.uOffset.value.set((targetX- this.offset.x) * 0.0005 , -(targetY- this.offset.y) * 0.0005 )
        // this.mesh.scale.set(this.sizes.x, this.sizes.y)
        this.mesh.position.set(this.offset.x - (window.innerWidth / 2)  , -this.offset.y   (window.innerHeight / 2), 0);

        // set uAlpha when list is hovered / unhovered
        this.linkHovered 
        ? this.uniforms.uAlpha.value = lerp(this.uniforms.uAlpha.value, 1.0, 0.1) 
        : this.uniforms.uAlpha.value = lerp(this.uniforms.uAlpha.value, 0.0, 0.1);
       
        
            for(let i = 0; i < this.links.length; i  ){
                if(this.linkHovered){
                    this.links[i].style.opacity = 0.2
                }else{
                    this.links[i].style.opacity = 1
                }
                
            
        }

        this.renderer.render(this.scene, this.camera);
        window.requestAnimationFrame(this.render.bind(this));
        
    }
    
}

new WebGL()

Hero.js

import React from 'react'
import ImageDistortion from './utils/ImageDistortion'


const Hero = () => {
    return (
        <div className="hero">
            <p>Hello.</p>
            <h1 id="three--anim--container">
                I'm <span className="name three--anim">Joris Delorme</span>, based in <span className="location three--anim">Lyon,France</span>.<br />
                I'm minimalist designer, developer and photographer.
            </h1>
        </div>
    )
}

export default Hero

CodePudding user response:

Your problem occurs because you're using document.querySelector() in a constructor, and React hasn't built the elements at that point. So when you're trying to invoke this.container.appendChild, the container doesn't yet exist, and you're getting null.

If you're going to use React with Three.js, I recommend you get acquainted with the lifecycle of React components, which teaches you when in the lifecycle HTML elements are created. You should also not be using document.querySelector() with React for this very reason. Instead you should be using React References to access DOM elements.

You're combining vanilla JS DOM manipulation with React, which are two different methods that can create conflicts when used simultaneously. You're gonna have to pick one or the other.

CodePudding user response:

When you import an image in React, you need to add the extension of the file you import. Like the following : import ImageDistortion from './utils/ImageDistortion.jpg'. or .png (depending on your image).

It works the same form svg, pdf videos and so on.

  • Related