GLTF loader loading model twice

Hey
Im loading glb animation using gltf loader but gltf loading model two time.
can anybody point where Im making mistake?

import React, {Component, useEffect, useRef, useState} from "react";
import * as THREE from "three";
import {RoomEnvironment} from 'three/examples/jsm/environments/RoomEnvironment.js';
import {OrbitControls} from "three/examples/jsm/controls/OrbitControls";
import {GLTFLoader} from "three/examples/jsm/loaders/GLTFLoader";
import {OBJLoader} from "three/examples/jsm/loaders/OBJLoader.js";
import {MTLLoader} from "three/examples/jsm/loaders/MTLLoader.js";
import {DRACOLoader} from 'three/examples/jsm/loaders/DRACOLoader.js';
import GallerModal from "../../../../assets/models/buildingmodel.glb"


function GalleryAnimation(props) {
    const ref = useRef(null);

    let camera, scene, renderer;
    var clock, mixer;

    const createCanvas = () => {

        const container = document.querySelector('#category_img');
        scene = new THREE.Scene();

        // const container = document.getElementById('CanvasFrame');
        renderer = new THREE.WebGLRenderer({antialias: true, alpha: true,});
        renderer.setPixelRatio(window.devicePixelRatio);
        renderer.setSize(window.innerWidth, window.innerHeight);

        //-----------Resizing Screen ------------//
        function resize() {
            renderer.setSize(container.clientWidth, container.clientHeight);
            camera.aspect = container.clientWidth * 2 / container.clientHeight;
            camera.updateProjectionMatrix();
            console.log("resize");
        };
        window.addEventListener("resize", resize);


        // container.appendChild(renderer.domElement);
        ref.current.appendChild(renderer.domElement);
        renderer.setClearColor(0xffffff, 0);

        const environment = new RoomEnvironment();
        const pmremGenerator = new THREE.PMREMGenerator(renderer);
        scene.environment = pmremGenerator.fromScene(environment).texture;

        renderer.toneMapping = THREE.ACESFilmicToneMapping;
        renderer.toneMappingExposure = 1;
        renderer.outputEncoding = THREE.sRGBEncoding;
        renderer.shadowMap.enabled = true;
        renderer.physicallyCorrectLights = true;

        clock = new THREE.Clock();

        // -------Loading Manager------//
        // const manager = new THREE.LoadingManager();
        //
        // manager.onLoad = function () {
        //
        //     console.log('everything loaded'); // debug
        //
        //     animate();
        //
        //
        // };


        //                 //   --------------------Gallery-Modal--------------------//
        var action;
        var loader = new GLTFLoader();


        let model;
        loader.load(GallerModal,  function (gltf) {

            model = gltf.scene;
            camera = gltf.cameras[0];
            model.position.set(0, 0, 0);
            scene.add(model);
            console.log(model, "scene")
            mixer = new THREE.AnimationMixer(gltf.scene);

            gltf.animations.forEach((clip) => {

                action = mixer.clipAction(gltf.animations[0]);
                action.clampWhenFinished = true;
                action.setLoop(THREE.LoopOnce);
                animate();
            },);

        });



//    document.addEventListener( 'mousedown', onDocumentMouseDown, false );

// function onDocumentMouseDown( event ) {

//   if ( action !== null ) {

//     action.stop();
//     action.play();

//   }

// }

        function lerp(x, y, a) {
            return (1 - a) * x + a * y
        }

// Used to fit the lerps to start and end at specific scrolling percentages
        function scalePercent(start, end) {
            return (scrollPercent - start) / (end - start)
        }

        const animationScripts = []

//add an animation that moves the cube through first 40 percent of scroll
        animationScripts.push({
            start: 5,
            end: 40,
            func: () => {

                console.log("hit")
                action.play();


                //console.log(cube.position.z)
            },
        })


        function playScrollAnimations() {
            animationScripts.forEach((a) => {
                if (scrollPercent >= a.start && scrollPercent < a.end) {
                    a.func()

                }
            })
        }

        let scrollPercent = 0

        document.body.onscroll = () => {
            //calculate the current scroll progress as a percentage
            scrollPercent =
                ((document.documentElement.scrollTop || document.body.scrollTop) /
                    ((document.documentElement.scrollHeight ||
                        document.body.scrollHeight) -
                        document.documentElement.clientHeight)) *
                100
            ;(document.getElementById('scrollProgress')).innerText =
                'Scroll Progress : ' + scrollPercent.toFixed(2)
        }

        window.scrollTo({top: 0, behavior: 'smooth'})


        // const size = 50;
        // const divisions = 10;

        // const gridHelper = new THREE.GridHelper(size, divisions);
        // scene.add(gridHelper);


        // const controls = new OrbitControls(camera, renderer.domElement);

        const animate = () => {
            requestAnimationFrame(animate);
            playScrollAnimations()

            var delta = clock.getDelta();

            mixer.update(delta);

            renderer.render(scene, camera);
        };


        // THREE.DefaultLoadingManager.onLoad = function () {
        //
        //     animate();
        // };

    }
    useEffect(() => {
        document.getElementById("category_img").innerHTML = "";

        createCanvas()


    },)

    return (
        <>
            <span id="scrollProgress"></span>
            <div id="category_img" className="category_img" ref={ref}/>
        </>

    )
}

export default GalleryAnimation;

react executes every effect twice in dev mode, to surface race conditions and missing clean up. two webglrenderers, two render loops, event handlers etc.

so what is a solution for that?

useEffect(() => {
  // init code here
  () => {
    // cleanup code here
  }
}, [])

every event you add gets removed in the cleanup, every model gets disposed, requestAnimationFrame → cancelAnimationFrame, etc. it won’t happen in production but it’s generally a good practice to make components clean. if your component mounts and leaves stuff behind on unmount that’s a memory leak, or worse, a condition for race, that’s why react is messing with it on purpose in dev mode, executing all effects twice is like whacking the bushes for issues.

do you have any example?
or you can edit my code?