Background I am trying to create a container for a collection of elements, each of which can be removed from the collection. When an element is removed, I want to animate its exit, and I am trying to achieve this using React Motion.
Here's a diagram of the above:
Problem I thought of using React Motion's TransitionMotion
component, and got stuck trying to write a function that needs to be passed to it. Here is my — incorrect — code:
class Container extends Component {
state = {
elementStyles: {}
}
componentDidMount() {
this.getElementStyles();
}
componentDidUpdate() {
this.getElementStyles();
}
getElementStyles() {
if (!this.props.children.length || this.hasElementStyles()) return;
// this assumes all elements passed to the container are of equal dimensions
let firstChild = this.refs.scroller.firstChild;
let elementStyles = {
width: firstChild.offsetWidth,
height: firstChild.offsetHeight,
opacity: 1
};
this.setState({
elementStyles
});
}
hasElementStyles() {
return !isEmpty(this.state.elementStyles); // lodash to the rescue
}
willLeave() {
return { width: spring(0), height: spring(0), opacity: spring(0) }
}
getChildrenArray() {
return Children.toArray(this.props.children); // that's React's util function
}
getModifiedChild(element, props) {
if (!element) return;
return React.cloneElement(
element,
{style: props.style}
);
}
getInitialTransitionStyles() {
let elements = this.getChildrenArray();
let result = elements.map((element, index) => ({
key: element.key,
style: this.state.elementStyles
}));
return result;
}
render() {
if (this.hasElementStyles()) {
return (
<TransitionMotion
willLeave={this.willLeave}
styles={this.getInitialTransitionStyles()}
>
{ interpolatedStyles => {
let children = this.getChildrenArray();
return <div ref="scroller" className="container">
{ interpolatedStyles.map((style, index) => {
return this.getModifiedChild(children[index], style);
})
}
</div>
}}
</TransitionMotion>
);
} else {
return (
<div ref="scroller" className="container">
{ this.props.children }
</div>
);
}
}
}
Notice this line inside the map function in the TransitionMotion component: return this.getModifiedChild(children[index], style)
. It is wrong, because once an element is removed from the collection, this.props.children
will change, and indices of the children
array will no longer correspond to the indices of the styles
array calculated from those children.
So I what I need is either some clever way to track the props.children
before and after an element has been removed from the collection (and I can't think of one; the best I could come up with was using a find
function on the array return this.getModifiedChild(children.find(child => child.key === style.key), style);
, but that will result in so many loops within loops I am scared even to think about it), or to use some completely different approach, which I am not aware of. Could you please help?