Thanks for the extra info. I can see now why from behaves like this.
I think it might be helpful to take React out of the equation and think about this similarly to a from call in a button click handler:
button.addEventListener('click', () => {
gsap.from(/* ... */)
})
Suppose the user clicks the button twice in quick succession — without any React. Then they will have the same problem. How would you recommend them to resolve it? Whatever the recommendation is, would likely work for React effects too. (Whether it's tracking animation state with a boolean, recommending a different API than from, or something else.)
From our side, we're happy to brainstorm solutions for other cases too — but it probably wouldn't be much different from this “double click” thought experiment.
We don't recommend people to create "overly generic" Hooks. If it has "effect" in the name or takes dependencies as arguments, it's probably not a good candidate for a custom Hook. It's hard to suggest something more specific though because I'm not familiar with common GSAP patterns.
The default behavior (without a ref) is usually the correct one. The reason we introduced it is we'd like to add a feature similar to KeepAlive in Vue that remounts a component with existing state. Like if you switch Feed -> Profile -> Feed, and we mount Feed with the previous state so that input aren't lost. To verify your components are resilient to this, we simulate unmounting and remounting with restored state right away. So this surfaces this issue. But this issue is what would happen if you were to quickly switch tabs yourself (when using our KeepAlive-like component). If you "solve" it with a ref, you're not gonna have an animation the second time at all. Or if you do it too quick it will similarly get stuck.
This is why I think the proper solution is to have the effect code be resilient to double-calling. Since double-calling is exactly what would happen if we actually remount the component with previous state and DOM before the animation has finished.