Closed charleshimmer closed 4 years ago
You need to render two different states, like you said, you need to change state. I recommend using requestAnimationFrame
in componentDidMount
.
So for my use case I have a list of child elements to be rendered. Items can be removed / added to this list at any time. When an item is added, I need to animate it's height from 0 to auto. When an item is removed, go from auto
to 0
.
What I am doing currently is something like this (using React Hooks):
Basically for every item I need to render I initialize it with a height value of 0, but then setState to set it to auto thereafter. How would you recommend I use requestAnimationFrame
?
type RowHeightMap = { [key: number]: boolean };
const [rowHeight, setRowHeight] = React.useState<RowHeightMap>({});
React.useEffect(() => {
if (!items) {
return;
}
setRowHeight(
field.value.reduce((acc: any, _: any, index: number) => {
acc[index] = true;
return acc;
}, {})
);
}, [items]);
return (
<div>
{items.map((row, index) => (
<AnimateHeight
key={row.__key}
duration={200}
height={rowHeight[index] ? 'auto' : 0}
easing="cubic-bezier(0.25, 0.25, 0.25, 1)"
animateOpacity
>
{row}
</AnimateHeight>
)}
</div>
);
To animate on add, I would create ListItem
which takes care of itself. Removing items is a little bit more complicated. If you don't want to handle states yourself, you might want to check https://github.com/reactjs/react-transition-group as it is outside of the scope of this library.
Hope that helps!
Yeah I'm familiar with react transition groups, but it suffers from the limitation of not being able to handle the animate to height: auto. What I have above works, I just wasn't sure if there was a better way I was overlooking.
Not sure how ListItem takes care of itself? Wouldn't it still need to start at height 0, then be flipped to height auto after initial mount? Removing was as simple as delaying the actual removal from the list till after the animation has ran. Like this:
const removeRow = (remove: any, i: number) => {
if (field.value.length > 1) {
const newValue = { ...rowHeights };
delete newValue[i];
setRowHeights(rowHeights);
setTimeout(() => remove(i), 200);
}
};
It is a little bit hard to explain so I create an example: https://codesandbox.io/s/react-animated-list-example-made-using-react-animate-height-je9q1?file=/src/App.js
It is not really a straight forward task, so there is hefty amount of state management we have to do on our own.
I made it using classes, as I'm still not super comfortable with hooks 🙈
Cheers!
P.S. I might add this to the documentation
Thanks for taking the time to write up a demo. We are basically doing the same thing except you have your organized a bit better with a List component and ListItem components. And I'm using hook's equivalent to componentDidMount to flip the height from 0 to auto where you're using requestAnimationFrame.
I'll probably tweak mine a bit based on some of your example's ideas. Thanks again!
You are welcome, glad it worked! It is abstract and a little bit hard to explain so I thought example would help! Cheers!
Yep def did. I ended up using requestAnimationFrame for mine as well and ended up making a ListItem like component which cleaned up the code a bit.
this looks to be what I was looking for but is there a way to animate the height from 0 to auto when a component is initially rendered? I'm sure I could do this manually with some state that I trip after the component is rendered, but curious if there is a better way to handle this case.