rive-app / rive-react

React runtime for Rive

Home Page:https://rive-app.github.io/rive-react

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

`useRive` React component lifecycle related init issue

glomotion opened this issue Β· comments

Hello there! πŸ‘‹

We are using useRive from this library for animations within our DesignSystem, and have noticed a pretty critical React component lifecycle related bug with the useRive hook. 😭
I've produced a simple reproduction of this bug in the latest published version of useRive from @rive-app/react-canvas@4.3.3

https://stackblitz.com/edit/stackblitz-starters-tc2yg6?file=app%2Fpage.tsx

Essentially, when first revealed (mounted), the Rive animation inits and autoplays as expected. However, once it's unmounted and then re-mounted (in this case using a simple conditional render bool) - the rive animation fails to init and autoplay.

Here is a simple code snippet which demonstrates the issue:

...
const [show, setShow] = useState(false);
const { RiveComponent } = useRive({
  src: 'https://cdn.rive.app/animations/vehicles.riv',
  autoplay: true,
  layout: new Layout({
    fit: Fit.Cover,
    alignment: Alignment.Center,
  }),
});

return (
  <div>
    <button onClick={() => setShow((old) => !old)}>toggle</button>
    {show && <RiveComponent style={{ width: '500px', height: '500px' }} />} {/*<-- this only loads up and plays the very first time it's mounted. :( */}
  </div>
 );
...

FWIW - I have tested out the bog standard <Rive /> React component and this does not suffer from the same issue. eg:

import Rive from '@rive-app/react-canvas';

<button onClick={() => setShow(old => !old)}>toggle</button>
      {show && (
        <Rive
          src="https://cdn.rive.app/animations/vehicles.riv"
          style={{ width: '400px', height: '400px' }}
        />
      )} {/* works as expected when toggling on and off */}

Hi @glomotion - thanks for reporting, and the repro. We'll take a look, as this is definitely not ideal in React dev! I suspect the issue might be here, where only when the canvas is mounted (aka, RiveComponent) is new Rive({}) actually called. When unmounted, we "cleanup" rive (which is a bunch of internal logic to delete cpp-created objects under the hood and stop the animation loop).

If it's feasible for you in the meantime until we give this another pass, as you saw in your second comment, wrapping your logic of using useRive in a wrapper component might do the trick temporarily, rather than using it with other React state that conditionally renders the <RiveComponent /> from useRive that would cause the above issues mentioned.

So:

// RiveWrapperComponent.jsx
const { RiveComponent } = useRive({
  src: 'https://cdn.rive.app/animations/vehicles.riv',
  autoplay: true,
  layout: new Layout({
    fit: Fit.Cover,
    alignment: Alignment.Center,
  }),
});

return (
  <RiveComponent style={{ width: '500px', height: '500px' }} />
 );
 // ParentComponent.jsx
 const [show, setShow] = useState(false);
 return (
  <div>
    <button onClick={() => setShow((old) => !old)}>toggle</button>
    {show && <RiveWrapperComponent />
  </div>
 );

Again, definitely recognize it's not ideal and might be a workaround for now! This also somewhat goes along with #107 too, which is also on the plate.

@zplata thanks for the suggested work around, did figure something like that might work.
We can perhaps suggest to our DS consumers to just wrap their rive instances inside components for now.

@zplata I wonder, is there any idea of rough timeline for when this kind of bug and #107 might be worked on? I'm mindful that #107 has already been open for more than a year. πŸ˜