Open mvasin opened 6 years ago
The existing enter
and exit
props could be used instead of transitionIsEnded
. So looks like the API change is again not needed. But <Transition>
doesn't listen for changes of those props, this must be fixed.
I'm not sure i understand where the current limitation is, why can't you pass a callback donw to the child component it can call to toggle in
?
@jquense done
is only available in addEndListener
function:
<Transition
addEndListener={(node, done) => /* you can use `done` here */ }
>
<Child setDone={done /* but `done` is not available here */ } />
</Transition>
How you can pull done
out of addEndListener
? You can save it to state of an outer component, but that will make the render function impure; that's a big red flag.
What we need is a prop coming from outer component that shortcuts any transitions <Transition>
thinks are running. And that shortcut is activated by <Child>
. done
function belongs to the outer component and triggers its state.
class Wrapper extends React.Component {
state = {isDone: false}
setDone = this.setState({isDone: true})
setUndone = this.setState({isDone: false})
render() {
return (
<Route path='/some-path'>
({match}) => (
<Transition
in={!!match}
mountOnEnter
unmountOnExit
appear={this.state.isDone}
enter={this.state.isDone}
exit={this.state.isDone}
onEntered={this.setUndone}
onExited={this.setUndone}
>
{transitionStage => <Child transitionStage={transitionStage} setDone={this.setDone} />}
</Transition>
)
</Route>
)
}
}
As soon as Child
is done, it triggers setDone
of the outer component, and state.done
from the outer component informs <Transition>
via enter
/ exit
props it should be done transitioning.
Looking at the docs, it could work like this, but it doesn't; setting enter
or exit
to false
in the middle of a transition doesn't end the transition.
One more thing to consider is appear enter={false}
behaviour. Those are somewhat contradictory options.
I'd prefer enter
to take precedence over appear
and disable all entering transitions including the first appearance, so in the case above enter={this.state.isDone}
would be enough. Currently it doesn't take precedence, and we need to set appear={this.state.isDone}
as well (and watch for appear
prop changes in the <Transition>
code).
But we can get along with it for now and maybe fix it in another PR.
I don't really understand the use-case here practically so i'm a bit reticent to extend or change the public API to allow for it. Generally cases like your above are handling by composing Transition and Child together into TransitioningChild, where the controls state is moved up to the parent, not down. Passing through the stage
and done feels like a leady encapsulation to me, but i don't have all the details.
If we are going to add something like this the right API is to pass done
to the child via the renderProp.
I would love to control the state up the tree instead of in the child, and that's what I tried in the first place, but there is a GSAP gotcha: is must know the exact DOM nodes as it declares the timeline. So timeline declaration is tied to the child, as DOM nodes are known only after component had been mounted. Weird, but if you declare GSAP timeline using ids or classes while DOM nodes didn't yet exist, it's not gonna work even you will mount components with those ids/classes later, and after that will play the timeline. Seems like an optimisation on GSAP side, to find the nodes once and never consider searching the DOM again.
That's why all I've left to do is to provide the done
callback to the child. It will be used on the GSAP timeline by the end of an animation.
Adding done
as the second parameter is a good idea as well, I'd be quite happy with that.
Do you want to request a feature or report a bug? A feature.
What is the current and the expected behavior? Let's imagine that
<Transition>
renders a child component, and the child component knows better when it's done transitioning (i.e. usesonComplete
GSAP event triggered at the end of animation timeline). It has nothing to do with listening to a CSStransitionend
event on a particular DOM node, because transition in the child component can involve multiple DOM nodes.Imagine a dialog:
<Transition>
: Get ready for your new job, startentering
!<Child>
: OK, I'll unpack my belongings and will tell when I'm ready to proceed.<Child>
: I'm done!<Transition>
: Good, you are inentered
status. ...<Transition>
: It's time for you to quit. Take you time as you areexiting
, I'll wait as long as you need.<Child>
: Thanks for waiting, I packed my stuff and ready for departure.<Transition>
: Great, now I kick you off. You'reexited
!At the moment the
react-transition-group
API does not allow such a dialog.One option we have to set timeout and try to match it with
<Child>
's transition end time, something that's error-prone and not always possible (think spring animations).The other option is to assign transition end listener function to
addEndListener
prop. But the problem here is passingdone
callback from the end listener function to the Child component.addEndListener={(_, done) => transitionIsEnded && done()}
also doesn't work because the function is triggered only once, whiletransitionIsEnded
isfalse
. And we can't savedone
to state becauserender()
must be idempotent.I'm still exploring how to tweak the API to allow the dialog above. One idea is to provide
<Transition>
withtransitionIsEnded
boolean prop. If it's true,<Transition>
will switch to the next stage (entering
->entered
orexiting
->exited
). If it's false, it will wait and switch on any of the following conditions:transitionIsEnded
is trueaddEndListener
triggereddone
callbacktimeout
expired.It implies there is an upper component that holds
transitionIsEnded
in state and gives a function to the<Child>
component to toggle that state.If you have any thoughts on this, please share.