[Help] useAnimations doesn't clean up animation state

To begin with all my models and animations are made by me in blender then i use a npx gltfjsx it autogenerates a jsx files out of the gltf file. Then i render the model and conditionally i play the animations.

The issue I’m having is when i play two similar animations one after the other i get this animation that is somewhere in the middle

The following is a code snippet of just the problematic bits

export const AnimationContext = React.createContext(false)  
  const [KFBeast16BGRAMBIGMBslot1, setKFBeast16BGRAMBIGMBslot1] = useState(false)
  const [KFBeast16BGRAMBIGMBslot2, setKFBeast16BGRAMBIGMBslot2] = useState(false)
  const [KFBeast16BGRAMBIGMBslot3, setKFBeast16BGRAMBIGMBslot3] = useState(false)
  const [KFBeast16BGRAMBIGMBslot4, setKFBeast16BGRAMBIGMBslot4] = useState(false)
  return (
    <div className={cases === "" ? 'not-mid' : 'mid'}>
      <div className={cases === "" ? 'mainView' : 'mainView-half'}>
        <AnimationContext.Provider
          value={{KFBeast16BGRAMBIGMBslot1: KFBeast16BGRAMBIGMBslot1,     setKFBeast16BGRAMBIGMBslot1: setKFBeast16BGRAMBIGMBslot1,
            KFBeast16BGRAMBIGMBslot2: KFBeast16BGRAMBIGMBslot2, setKFBeast16BGRAMBIGMBslot2: setKFBeast16BGRAMBIGMBslot2,
            KFBeast16BGRAMBIGMBslot3: KFBeast16BGRAMBIGMBslot3, setKFBeast16BGRAMBIGMBslot3: setKFBeast16BGRAMBIGMBslot3,
            KFBeast16BGRAMBIGMBslot4: KFBeast16BGRAMBIGMBslot4,      setKFBeast16BGRAMBIGMBslot4: setKFBeast16BGRAMBIGMBslot4}}>
          <MainView />
        </AnimationContext.Provider>
      <div className={cases === "" ? 'hidden' : 'show'}>
        <AnimationContext.Provider
          value={{KFBeast16BGRAMBIGMBslot1: KFBeast16BGRAMBIGMBslot1, setKFBeast16BGRAMBIGMBslot1: setKFBeast16BGRAMBIGMBslot1,
            KFBeast16BGRAMBIGMBslot2: KFBeast16BGRAMBIGMBslot2, setKFBeast16BGRAMBIGMBslot2: setKFBeast16BGRAMBIGMBslot2,
            KFBeast16BGRAMBIGMBslot3: KFBeast16BGRAMBIGMBslot3, setKFBeast16BGRAMBIGMBslot3: setKFBeast16BGRAMBIGMBslot3,
            KFBeast16BGRAMBIGMBslot4: KFBeast16BGRAMBIGMBslot4, setKFBeast16BGRAMBIGMBslot4: setKFBeast16BGRAMBIGMBslot4
          }}>
          <Collection />
        </AnimationContext.Provider>
      </div>
    </div>

export default App

Then in mainview. its only important that i call

<CoolerMasterCaseMaker5t />

CoolerMasterCaseMaker5t is the autogenerated jsx file

import { useGLTF, useAnimations } from '@react-three/drei'
import { AnimationContext } from '../../App'
const CoolerMasterCaseMaker5t = (props) => {
  const group = useRef()
  const { nodes, materials, animations } = useGLTF('/pc_parts/pc_cases/CoolerMasterCaseMaker5t.glb')
  const { actions, mixer } = useAnimations(animations, group)
  const { KFBeast16BGRAMBIGMBslot1, setKFBeast16BGRAMBIGMBslot1,
 KFBeast16BGRAMBIGMBslot2, setKFBeast16BGRAMBIGMBslot2,
KFBeast16BGRAMBIGMBslot3, setKFBeast16BGRAMBIGMBslot3,
 KFBeast16BGRAMBIGMBslot4, setKFBeast16BGRAMBIGMBslot4 } = useContext(AnimationContext)

 const playAnimation = (anime) => {
    anime.loop = 0
    anime.repetitions = 0
    anime.play()
    anime.clampWhenFinished = true
  }

  useEffect(() => {
    if (KFBeast16BGRAMBIGMBslot1) {
      playAnimation (actions.Beast8GBSlot1BIGMB)
    } else {
      actions.Beast8GBSlot1BIGMB.stop()
    } (all other animations are called the same way)
  },[KFBeast16BGRAMBIGMBslot1]

return (big chunk of code like the following )
        <group name="Kingston_Fury_Beast_CL18_DDR4_8GB" position={[-0.915, 2.01, -0.002]} scale={0}>
          <mesh name="Cube199" geometry={nodes.Cube199.geometry} material={materials['ktc beast frontside']} />
          <mesh name="Cube199_1" geometry={nodes.Cube199_1.geometry} material={materials['ktc beast backside']} />
        </group>
  )
}

useGLTF.preload('/pc_parts/pc_cases/CoolerMasterCaseMaker5t.glb')

export default CoolerMasterCaseMaker5t

Collection is nothing more then just calling

<RAMLogic />

and in RAMLogic i have the following

import React, { useContext, useEffect, useRef, useState } from 'react'
import { AnimationContext } from '../../App'


const CASE_SIZE = { L: "bigCase", M: "mediumCase", S: "smallCase" }
const MB_SIZE = { L: 'bigMB', M: "mediumMB", S: "smallMB" }

const RAMLogic = () => {
    const {
        cases, mb, setCases, setMB, lCaseBigMBSlide, lCaseMedMBSlide, lCaseSmlMBSlide,
        KFBeast16BGRAMBIGMBslot1, KFBeast16BGRAMBIGMBslot2, KFBeast16BGRAMBIGMBslot3, KFBeast16BGRAMBIGMBslot4,
        KFBeast16BGRAMMEDMBslot1, KFBeast16BGRAMMEDMBslot2, KFBeast16BGRAMMEDMBslot3, KFBeast16BGRAMMEDMBslot4,
        KFBeast16BGRAMSMLMBslot1, KFBeast16BGRAMSMLMBslot2,
        HPV8GBRAMBIGMBslot1, HPV8GBRAMBIGMBslot2, HPV8GBRAMBIGMBslot3, HPV8GBRAMBIGMBslot4,
        HPV8GBRAMMEDMBslot1, HPV8GBRAMMEDMBslot2, HPV8GBRAMMEDMBslot3, HPV8GBRAMMEDMBslot4,
        HPV8GBRAMSMLMBslot1, HPV8GBRAMSMLMBslot2,
        setKFBeast16BGRAMBIGMBslot1, setKFBeast16BGRAMBIGMBslot2, setKFBeast16BGRAMBIGMBslot3, setKFBeast16BGRAMBIGMBslot4,
        setKFBeast16BGRAMMEDMBslot1, setKFBeast16BGRAMMEDMBslot2, setKFBeast16BGRAMMEDMBslot3, setKFBeast16BGRAMMEDMBslot4,
        setKFBeast16BGRAMSMLMBslot1, setKFBeast16BGRAMSMLMBslot2,
        setHPV8GBRAMBIGMBslot1, setHPV8GBRAMBIGMBslot2, setHPV8GBRAMBIGMBslot3, setHPV8GBRAMBIGMBslot4,
        setHPV8GBRAMMEDMBslot1, setHPV8GBRAMMEDMBslot2, setHPV8GBRAMMEDMBslot3, setHPV8GBRAMMEDMBslot4,
        setHPV8GBRAMSMLMBslot1, setHPV8GBRAMSMLMBslot2, beast8GBRam, setbeast8GBRam, HP16BGRam, setHP16BGRam
    } = useContext(AnimationContext);

    const [kSlot1, setKslot1] = useState(false)
    const [kSlot2, setKslot2] = useState(false)
    const [kSlot3, setKslot3] = useState(false)
    const [kSlot4, setKslot4] = useState(false)

    const [vSlot1, setVslot1] = useState(false)
    const [vSlot2, setVslot2] = useState(false)
    const [vSlot3, setVslot3] = useState(false)
    const [vSlot4, setVslot4] = useState(false)

    const onKFBeast16BGRAM = () => {
        if (cases !== "" && mb !== "") {
            setbeast8GBRam(!beast8GBRam)
            if (KFBeast16BGRAMBIGMBslot1 == true)
                setKFBeast16BGRAMBIGMBslot1(false)
            if (KFBeast16BGRAMBIGMBslot2 == true)
                setKFBeast16BGRAMBIGMBslot2(false)
            if (KFBeast16BGRAMBIGMBslot3 == true)
                setKFBeast16BGRAMBIGMBslot3(false)
            if (KFBeast16BGRAMBIGMBslot4 == true)
                setKFBeast16BGRAMBIGMBslot4(false)

        }
    }

    const onHPV8GBRAM = () => {
        if (cases !== "" && mb !== "") {
            setHP16BGRam(!HP16BGRam)
            if (HPV8GBRAMBIGMBslot1 == true)
                setHPV8GBRAMBIGMBslot1(false)
            if (HPV8GBRAMBIGMBslot2 == true)
                setHPV8GBRAMBIGMBslot2(false)
            if (HPV8GBRAMBIGMBslot3 == true)
                setHPV8GBRAMBIGMBslot3(false)
            if (HPV8GBRAMBIGMBslot4 == true)
                setHPV8GBRAMBIGMBslot4(false)
        }
    }

    useEffect(() => {
        if (KFBeast16BGRAMBIGMBslot1 == true || KFBeast16BGRAMMEDMBslot1 == true || KFBeast16BGRAMSMLMBslot1 == true)
            setKslot1(true)
        else
            setKslot1(false)
        if (KFBeast16BGRAMBIGMBslot2 == true || KFBeast16BGRAMMEDMBslot2 == true || KFBeast16BGRAMSMLMBslot2 == true)
            setKslot2(true)
        else
            setKslot2(false)
        if (KFBeast16BGRAMBIGMBslot3 == true || KFBeast16BGRAMMEDMBslot3 == true)
            setKslot3(true)
        else
            setKslot3(false)
        if (KFBeast16BGRAMBIGMBslot4 == true || KFBeast16BGRAMMEDMBslot4 == true)
            setKslot4(true)
        else
            setKslot4(false)
        if (HPV8GBRAMBIGMBslot1 == true || HPV8GBRAMMEDMBslot1 == true || HPV8GBRAMSMLMBslot1 == true)
            setVslot1(true)
        else
            setVslot1(false)
        if (HPV8GBRAMBIGMBslot2 == true || HPV8GBRAMMEDMBslot2 == true || HPV8GBRAMSMLMBslot2 == true)
            setVslot2(true)
        else
            setVslot2(false)
        if (HPV8GBRAMBIGMBslot3 == true || HPV8GBRAMMEDMBslot3 == true)
            setVslot3(true)
        else
            setVslot3(false)
        if (HPV8GBRAMBIGMBslot4 == true || HPV8GBRAMMEDMBslot4 == true)
            setVslot4(true)
        else
            setVslot4(false)
    }, [KFBeast16BGRAMBIGMBslot1, KFBeast16BGRAMBIGMBslot2, KFBeast16BGRAMBIGMBslot3, KFBeast16BGRAMBIGMBslot4,
        HPV8GBRAMBIGMBslot1, HPV8GBRAMBIGMBslot2, HPV8GBRAMBIGMBslot3, HPV8GBRAMBIGMBslot4,
        KFBeast16BGRAMMEDMBslot1, KFBeast16BGRAMMEDMBslot2, KFBeast16BGRAMMEDMBslot3, KFBeast16BGRAMMEDMBslot4,
        HPV8GBRAMMEDMBslot1, HPV8GBRAMMEDMBslot2, HPV8GBRAMMEDMBslot3, HPV8GBRAMMEDMBslot4,
        KFBeast16BGRAMSMLMBslot1, KFBeast16BGRAMSMLMBslot2, HPV8GBRAMSMLMBslot1, HPV8GBRAMSMLMBslot2])



    const onKRAMslot1 = () => {
        if (cases == CASE_SIZE.L && mb == MB_SIZE.L) {
            setKFBeast16BGRAMBIGMBslot1(!KFBeast16BGRAMBIGMBslot1)
        }
        if (cases == CASE_SIZE.L && mb == MB_SIZE.M) {
            setKFBeast16BGRAMMEDMBslot1(!KFBeast16BGRAMMEDMBslot1)
        }
        if (cases == CASE_SIZE.L && mb == MB_SIZE.S) {
            setKFBeast16BGRAMSMLMBslot1(!KFBeast16BGRAMSMLMBslot1)
        }
    }

    const onKRAMslot2 = () => {
        if (cases == CASE_SIZE.L && mb == MB_SIZE.L) {
            setKFBeast16BGRAMBIGMBslot2(!KFBeast16BGRAMBIGMBslot2)
        }
        if (cases == CASE_SIZE.L && mb == MB_SIZE.M) {
            setKFBeast16BGRAMMEDMBslot2(!KFBeast16BGRAMMEDMBslot2)
        }
        if (cases == CASE_SIZE.L && mb == MB_SIZE.S) {
            setKFBeast16BGRAMSMLMBslot2(!KFBeast16BGRAMSMLMBslot2)
        }
    }

    const onKRAMslot3 = () => {
        if (cases == CASE_SIZE.L && mb == MB_SIZE.L) {
            setKFBeast16BGRAMBIGMBslot3(!KFBeast16BGRAMBIGMBslot3)
        }
        if (cases == CASE_SIZE.L && mb == MB_SIZE.M) {
            setKFBeast16BGRAMMEDMBslot3(!KFBeast16BGRAMMEDMBslot3)
        }
    }

    const onKRAMslot4 = () => {
        if (cases == CASE_SIZE.L && mb == MB_SIZE.L) {
            setKFBeast16BGRAMBIGMBslot4(!KFBeast16BGRAMBIGMBslot4)
        }
        if (cases == CASE_SIZE.L && mb == MB_SIZE.M) {
            setKFBeast16BGRAMMEDMBslot4(!KFBeast16BGRAMMEDMBslot4)
        }
    }

    const onVRAMslot1 = () => {
        if (cases == CASE_SIZE.L && mb == MB_SIZE.L) {
            setHPV8GBRAMBIGMBslot1(!HPV8GBRAMBIGMBslot1)
        }
        if (cases == CASE_SIZE.L && mb == MB_SIZE.M) {
            setHPV8GBRAMMEDMBslot1(!HPV8GBRAMMEDMBslot1)
        }
        if (cases == CASE_SIZE.L && mb == MB_SIZE.S) {
            setHPV8GBRAMSMLMBslot1(!HPV8GBRAMSMLMBslot1)
        }
    }

    const onVRAMslot2 = () => {
        if (cases == CASE_SIZE.L && mb == MB_SIZE.L) {
            setHPV8GBRAMBIGMBslot2(!HPV8GBRAMBIGMBslot2)
        }
        if (cases == CASE_SIZE.L && mb == MB_SIZE.M) {
            setHPV8GBRAMMEDMBslot2(!HPV8GBRAMMEDMBslot2)
        }
        if (cases == CASE_SIZE.L && mb == MB_SIZE.S) {
            setHPV8GBRAMSMLMBslot2(!HPV8GBRAMSMLMBslot2)
        }
    }

    const onVRAMslot3 = () => {
        if (cases == CASE_SIZE.L && mb == MB_SIZE.L) {
            setHPV8GBRAMBIGMBslot3(!HPV8GBRAMBIGMBslot3)
        }
        if (cases == CASE_SIZE.L && mb == MB_SIZE.M) {
            setHPV8GBRAMMEDMBslot3(!HPV8GBRAMMEDMBslot3)
        }
    }

    const onVRAMslot4 = () => {
        if (cases == CASE_SIZE.L && mb == MB_SIZE.L) {
            setHPV8GBRAMBIGMBslot4(!HPV8GBRAMBIGMBslot4)
        }
        if (cases == CASE_SIZE.L && mb == MB_SIZE.M) {
            setHPV8GBRAMMEDMBslot4(!HPV8GBRAMMEDMBslot4)
        }
    }

    return (
        <>
            <div className='width33'>
                <div onClick={onKFBeast16BGRAM} className={((lCaseBigMBSlide == true || lCaseMedMBSlide == true || lCaseSmlMBSlide == true) && beast8GBRam == true) ? 'box-active' : 'box-not-active'}>
                    {/* <KingstoneFuryBeastCanvas /> */}
                    <div>KingstoneFurybeast</div>
                </div>
                <div className={((lCaseBigMBSlide == true || lCaseMedMBSlide == true || lCaseSmlMBSlide == true) && beast8GBRam == true) ? 'visible' : 'hidden'}>
                    <button disabled={vSlot1 ? true : false} onClick={onKRAMslot1} className={(vSlot1) ? 'ramButton-disabled' : 'ramButton-enabled'}>Slot1</button>
                    <button disabled={vSlot2 ? true : false} onClick={onKRAMslot2} className={(vSlot2) ? 'ramButton-disabled' : 'ramButton-enabled'}>Slot2</button>
                    <button disabled={vSlot3 ? true : false} onClick={onKRAMslot3} className={(vSlot3) ? 'ramButton-disabled' : 'ramButton-enabled'}>Slot3</button>
                    <button disabled={vSlot4 ? true : false} onClick={onKRAMslot4} className={(vSlot4) ? 'ramButton-disabled' : 'ramButton-enabled'}>Slot4</button>
                </div>
            </div>
            <div className='width33'>
                <div onClick={onHPV8GBRAM} className={((lCaseBigMBSlide == true || lCaseMedMBSlide == true || lCaseSmlMBSlide == true) && HP16BGRam == true) ? 'box-active' : 'box-not-active'}>
                    <div>HPV8GBDDR4</div>
                    {/* <HPV8GBDDR4Canvas /> */}
                </div>
                <div className={((lCaseBigMBSlide == true || lCaseMedMBSlide == true || lCaseSmlMBSlide == true) && HP16BGRam == true) ? 'visible' : 'hidden'}>
                    <button disabled={kSlot1 ? true : false} onClick={onVRAMslot1} className={(kSlot1) ? 'ramButton-disabled' : 'ramButton-enabled'}>Slot1</button>
                    <button disabled={kSlot2 ? true : false} onClick={onVRAMslot2} className={(kSlot2) ? 'ramButton-disabled' : 'ramButton-enabled'}>Slot2</button>
                    <button disabled={kSlot3 ? true : false} onClick={onVRAMslot3} className={(kSlot3) ? 'ramButton-disabled' : 'ramButton-enabled'}>Slot3</button>
                    <button disabled={kSlot4 ? true : false} onClick={onVRAMslot4} className={(kSlot4) ? 'ramButton-disabled' : 'ramButton-enabled'}>Slot4</button>
                </div>
            </div>
            <div className='width33'>
                <div className='box-not-active'>
                    {/* <CorsairVengeanceCanvas /> */}
                    <div>CorsairVengeance "NOT THIS"</div>
                </div>
            </div>

        </>
    )
}

export default RAMLogic

the problem happens when i click the buttons for slot1 and slot2 or any other combination.
Other animation work just fine

Video demo

useEffect allows you to control side effects, it has a clean up phase. if used properly it doesn’t leave a trace, if it opens a socket for instance, it will close it on unmount.

useEffect(() => {
  socket.open()
  return () => {
    socket.close()
  }
}, [socket])

here’s a small example that shows how to control animations, switching between actions GLTF Animations - CodeSandbox

ps your code above looks too much, too complicated. all these ifs and switches, tons of stuff inside context. there has to be a simpler, better way.

for instance, instead of hundreds of switch cases

if (slot1) setHPV8GBRAMMEDMBslot1()
else if (slot2) setHPV8GBRAMMEDMBslot2()
else if (slot3) setHPV8GBRAMMEDMBslot3()
...

you should do

setHPV8GBRAMMEDMBslot(slot)

Thank you for the replay i sometimes get ahead of myself before responding :sweat_smile:
On the note of to many if/else i agree i am a beginner and this is my first project ever (its for personal use) so im trying it improve. Even as i am doing this project i noticed instead of declaring individual states for each slot i made an array (for a different component) and planning on going back and refactoring the whole code once all is operational.

  let corsairAFSLIMBIG = []


  for (let i = 1; i < 3; i++) {
    corsairAFSLIMBIG.push({
      key: "Slot" + i + " 92mm",
      slot: false
    })
    corsairAFSLIMBIG.push({
      key: "Slot" + i + " 120mm",
      slot: false
    })
    corsairAFSLIMBIG.push({
      key: "Slot" + i + " 140mm",
      slot: false
    })
  }
  }

  const [CorsairAFSLIMBIGs, setCorsairAFSLIMBIGs] = useState(corsairAFSLIMBIG)

same with the buttons i have the following react component

import React from 'react'

const FanButton = ({ fans, size, setFan }) => {

    const changeState = (id, next) => {
        setFan(fans.map(fan => {
            if (fan.key === id) {
                return { ...fan, slot: next }
            } else {
                return fan
            }
        }))
    }
    const getButtonText = (e) => {
        if (e.includes(size)) {
            return e
        }
    }
    return (
        <>
            {fans.map((e) => (
                <button className={(getButtonText(e.key)) ? "visible" : "hidden"}
                    id={e.key}
                    onClick={() => changeState(e.key, !e.slot)}>
                    {getButtonText(e.key)}
                </button>
            ))}
        </>
    )
}

export default FanButton

But going back to the useEffect all my animations are done in one useEffect with again alot of if/else only because i could not figure out how to get the specific animation from actions so will this work if i have multiple like the following? (if i got what u meant correctly

useEffect(() => {
    if (KFBeast16BGRAMBIGMBslot1) {
      playAnimation (actions.Beast8GBSlot1BIGMB)
    } else {
      return () => {actions.Beast8GBSlot1BIGMB.stop()}
    }
 if (KFBeast16BGRAMBIGMBslot2) {
      playAnimation (actions.Beast8GBSlot2BIGMB)
    } else {
      return () => {actions.Beast8GBSlot2BIGMB.stop()}
    }
  },[KFBeast16BGRAMBIGMBslot1, KFBeast16BGRAMBIGMBslot2 ]

put whatever action you want to play into a variable, start it, stop it in the clanup. if you don’t want abrupt changes you can fade the actions, don’t forget the reset. instead of switch cases use object string lookup

useEffect(() => {
  // slot === "Beast8GBSlot1BIGMB" | "Beast8GBSlot1BIGM2" | ...
  const currentAction = actions[slot]
  currentAction.reset().fadeIn(0.5).play()
  return () => currentAction.fadeOut(0.5)
}, [...])

if i was you i’d not jump ahead, learn some more javascript basics, if else object array for while function, all this stuff. then some 2015 javascript Learn ES2015 · Babel threejs and react will be much more effortless for you.

as a rule of thumb, whenever you feel like it’s getting tiring, or you’re repeating code, something is wrong. better to stop right then and analyse the situation.

The problem here is that i need something to trigger the animation that is where the if/else cases come into play. The website i am making is a build your pc sort of thing. I want the website to be interactive when the user clicks a button to chose a motherboard for the animation to play and then clampWhenFinished so the animation does not play again. Also i don’t want to reset the animations because imagine you are building a pc and the part u had chosen before just disappears. The goal is for the user to pick all the parts see the product as they are building it in font of them. But all was going ok until this particular case where the two animations are sort of merging together. You can see that in the video i linked of the two animations merging in my original post

Also sorry for bothering i sort of have nowhere else to turn to, to ask and I’m getting frustrated trying to deal with this issue for a while now and thank you for the advise ill look into more of the basics and some ES2015