I'm trying to animate in/out a single component when mounted, however am struggling to find a basic example that doesn't animate a list/internal state/etc.
I might be misunderstanding how react-motion works, is this possible in a simple way?
// Transition container
class Transition extends React.Component {
willLeave() {
return {opacity: spring(0)}
}
willEnter() {
return {opacity: spring(1)}
}
getStyles() {
return [{key: 'one', style: {opacity: spring(1)} }]
}
render() {
return (
<div>
<TransitionMotion styles={this.getStyles()} willLeave={this.willLeave} willEnter={this.willEnter}>
{int => <div style={{opacity: int.opacity}}> hello there </div>}
</TransitionMotion>
</div>
)
}
}
// Main container
export default class Container extends React.Component {
constructor(){
super()
this.state = {
openCard: false,
}
}
openCard(props) {
this.setState({ openCard: !this.state.openCard })
}
render() {
return <div> {this.state.openCard ? <Transition/> : null } </div>
)
}
}
I am using it for Route transitions, which is essentially the same thing (swapping out a single component). Here is my full RouteTransition component:
import React from 'react';
import { TransitionMotion, spring } from 'react-motion';
const willEnter = () => ({
opacity: 0,
scale: 0.98
});
const willLeave = () => ({
opacity: spring(0),
scale: spring(1.02)
});
const getStyles = () => ({
opacity: spring(1),
scale: spring(1)
});
const RouteTransition = ({ children: child, pathname }) => (
<TransitionMotion
styles={ [{
key: pathname,
style: getStyles(),
data: { child }
}] }
willEnter={ willEnter }
willLeave={ willLeave }
>
{ (interpolated) =>
<div>
{ interpolated.map(({ key, style, data }) =>
<div
key={ `${key}-transition` }
style={ {
...styles.wrapper,
opacity: style.opacity,
transform: `scale(${style.scale})`
} }
>
{ data.child }
</div>
) }
</div>
}
</TransitionMotion>
);
var styles = {
wrapper: {
position: 'absolute',
width: '100%'
}
};
export default RouteTransition;
It is used like this:
<RouteTransition pathname={ location.pathname }>
{ this.props.children /* current route component */ }
</RouteTransition>
Hmm i've reused what you've suggested, however it doesn't seem like 'willEnter' and 'willLeave' are being triggered. The component is mounted, but no animation takes place.
const willLeave = () => ({
opacity: spring(0)
})
const willEnter = () => ({
opacity: 0
})
const getStyles = () => ({
opacity: spring(1)
})
const Transition = ({ children }) =>
<TransitionMotion
styles={[{ key: 'key', style: getStyles(), data: children }]}
willLeave={willLeave}
willEnter={willEnter}>
{ int =>
<div>
{int.map(({ key, style, data }) =>
<div key={`${key}-transition`} style={{opacity: style.opacity}}>
{data}
</div>
)}
</div>}
</TransitionMotion>
Do you think it might have something to do with the way I'm using it?
{this.state.openCard ? <Transition><div>Hello There</div></Transition> : null }
Interpolated only returns the styles object, no values in there seem to change.
Thanks for your help!
You are removing the Transition component when this.state.openCard is false. Try:
<Transition>
{ this.state.openCard ? <div>Hello There</div> : null }
</Transition>
Still isn't working as expected! what version of react-motion are you using?
Still isn't working as expected! what version of react-motion are you using?
*_Disclaimer: I have only begun programming a few months ago so take everything with a grain of salt.
*_
I believe TransitionMotion needs to receive dynamic changes for it to trigger.
{this.state.openCard ? <Transition><div>Hello There</div></Transition> : null }
This would just render the Transition component and neither TransitionMotion nor it's container animates itself. Only TransitionMotion's children are animated if they receive dynamic changes, state/props.
<Transition>
{ this.state.openCard ? <div>Hello There</div> : null }
</Transition>
Transition is not receiving any changes that would incur it to animate, that is because the RouteTransition component is the one that gets animated, not the child directly, so you have to pass dynamic data to RouteTransition component to get the animation to trigger.
So if you were to pass different children dynamically or a different key like key: Date.now() + 'coerceToString' it would trigger a render which would trigger the animation.
The way TransitionMotion itself works is a bit harder for me to grok since it keeps a removed child around until the component reaches the willLeave's value so I am unable to confirm the required uniqueness of key, whether it can be a unique but static value.
I hope someone will provide a better response as I hope to understand how TransitionMotion works as well.
@boyswan My component is guaranteed to have a child, but yours isn't.
// this array should be empty if there are no children
styles={[{ key: 'key', style: getStyles(), data: children }]}
You would have to do something like:
styles={ children ? [{ key: 'key', style: getStyles(), data: children }] : [] }
It's easier if you're already dealing with an array. In that case you just use .map:
styles={ items.map(item => ({ key: item.id, style: getStyles(), data: item }) }
Unfortunately in React, this.props.children can be either an array or a single element.
Perfect, working now! thank you both for your help
What is the full example? I'm strugling to piece it together from above.
const willLeave = () => ({
opacity: spring(0)
})
const willEnter = () => ({
opacity: 0
})
const getStyles = () => ({
opacity: spring(1)
})
const Transition = ({ children }) =>
willLeave={willLeave}
willEnter={willEnter}>
{ int =>
just keep children is array
Most helpful comment
I am using it for Route transitions, which is essentially the same thing (swapping out a single component). Here is my full RouteTransition component:
It is used like this: