Search code examples
reactjsreact-nativelottiereact-native-web

let variable is undefined in useImperativeHandle function


I work with the lottie-web package in a .web.js file under React Native (Expo).

My problem is that the let variable anim is undefined in the functions in useImperativeHandle. In the useEffect anim works wonderfully. For example if I access anim.play() right after the initialize at useEffect it works. But at the play() function in imperative it doesn't work.

At my parent component I create a ref with useRef and pass the ref to the component

const lottieAnim = useRef(null);

--

let anim;
useEffect(() => {
  const lottieOptions = {
    container: divContainer.current,
    renderer: 'svg',
    loop,
    autoplay,
    segments: segments !== false,
    animationData,
    rendererSettings,
  };
  anim = lottie.loadAnimation({ ...lottieOptions, ...options });
  // anim.play(); works here 

  if (onAnimationFinish) {
    anim.addEventListener('complete', onAnimationFinish);
  }
}, []);

useImperativeHandle(ref, () => ({
  stop() {
    anim.stop();
  },
  pause() {
    anim.pause();
  },
  play() {
    anim.play();
    // anim is undefined
  },
  setSpeed(s) {
    anim.setSpeed(s);
  },
  setDirection(d) {
    anim.setDirection(d);
  },
  playSegments(s) {
    anim.playSegments(s);
  },
}));

Solution

  • Update 2023

    I've created a package for connecting third-party packages to react via useImperativeHandler in an easier way, you might find it useful and easier to use 😊.
    react-aptor

    That is because React has no clue what anim is when it creates API functions in useImperativeHandle (duo to closure and react updating strategies that don't trigger any update by mutating a variable). There is some way you can handle this, after all, it comes to personal opinion what to do, I will use something like this which work the best for me.

    Add GetApi Function

    // hanlder.js
    const stop = anim => anim.stop()
    // api.js
    const getAPI = anim => () => ({
      stop: stop.bind(this, anim),
      // or simply
      setSpeed: s => anim.setSpeed(s),
    
      // you can mock APIs here if anim is null or throw an Error
    });
    

    store anim in a state

    store anim in the state for the first render only and use it in the dependencies array of getApi useEffect

    const [anim, setAnim] = React.useState(null);
    
    React.useEffect(() => {
      // initialization part
    }, []);
    
    React.useImperativeHandle(ref, getAPI(anim), [anim]);