Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add useAnimationState hook #134

Open
leroykorterink opened this issue May 4, 2023 · 1 comment
Open

Add useAnimationState hook #134

leroykorterink opened this issue May 4, 2023 · 1 comment
Assignees
Labels
enhancement New feature or request good first issue Good for newcomers help wanted Extra attention is needed

Comments

@leroykorterink
Copy link
Collaborator

It's currently not possible to use animation updates inside a component. You need to workaround it using the useExposeAnimation and useExposedAnimation hooks.

const isDesktop = useMediaQuery(...)
const myAnimation = useAnimation(() => createAnimation(refs, isDesktop), [isDesktop])

// Hacky workaround
useExposeAnimation(ref, myAnimation);
const myAnimationState = useExposedAnimation(ref, myAnimation);

Solution 1

Create a hook that uses the workaround solution.

function useAnimationState(animation: RefObject<gsap.core.Animation>): gsap.core.Animation | undefined {
  const ref = useRef(Symbol("useAnimationState"));
  useExposeAnimation(animation, ref);
  return useExposedAnimation(ref);
}

Solution 2

Create an animation hook that uses state to store the animation instance.

function useAnimationState<T extends gsap.core.Animation>(
  callback: () => T | undefined,
  dependencies: ReadonlyArray<unknown>,
): T | undefined {
  const [animation, setAnimation] = useState<T>();

  // eslint-disable-next-line react-hooks/exhaustive-deps
  const _callback = useCallback(callback, dependencies);

  useEffect(() => {
    const _animation = _callback();

    setAnimation(_animation)

    return () => {
      _animation?.kill();
    };
  }, [_callback]);

  return animation;
}

If we use this approach we also need to update the useExposeAnimation hook so that it accepts RefObjects<gsap.core.Animation> and gsap.core.Animation.

@leroykorterink leroykorterink added enhancement New feature or request good first issue Good for newcomers help wanted Extra attention is needed labels May 4, 2023
@ThaNarie
Copy link
Member

ThaNarie commented May 4, 2023

For completeness, there is a third solution, but this is error prone, as you would need to keep track of your dependencies and keep them in sync. Also, it might be more complicated when passing things around in hooks.

useEffect(() => {
  // myAnimation.current has changed

  // use the same deps as your animation
}, [isDesktop]);

For solution 2, you would end up with two similar hooks, one returning a ref, and the other returning a "state"?

const myAnimationRef = useAnimation(() => createAnimation(refs, isDesktop), [isDesktop])
const myAnimationState = useAnimationState(() => createAnimation(refs, isDekstop), [isDesktop])

It would indeed be very straightforward to have useExposeAnimation accept both a ref and a state variable.

We might consider renaming useAnimation to useAnimationRef to always have it clear which version you are using?
Not sure if we have a preference for either one for normal use.


I think solution 1 can become more generic; we don't have to rely on expose/exposed, the global store, triggering listeners.
The only reason solution 1 could work in the first place, is that we know that the hook gets executed when the component rerenders, and at that point we might have a new animation available on the ref.
If that wasn't the case, there is no way to reliably sync a ref, as it can also be set outside of rerender cycle.

With that in mind, I think we can basically do:

function useAnimationState(animation: RefObject<gsap.core.Animation>): gsap.core.Animation | undefined {
  const [state, setState] = useState(animation.current);
  setState(animation.current);
  return state;
}

Thinking about it, we could just return animation.current from that function.

Because technically this would also work:

const isDesktop = useMediaQuery(...)
const myAnimation = useAnimation(() => createAnimation(refs, isDesktop), [isDesktop])

useEffect(() => {

}, [myAnimation.current]);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request good first issue Good for newcomers help wanted Extra attention is needed
Projects
None yet
Development

No branches or pull requests

3 participants