Question:
I have a React functional component that recursively renders nested lists. That part is working fine.
The part that I am struggling with is getting the divs that contain the nested lists to have an expanding animation when the nested lists (of varying sizes) appear and disappear.
Because the amount of content is not known, simply animating the max-height property does not work. For example, when one level of lists is rendered, the height of the frame might expand to 100px. However, if you animate the max-height to 100px, then the div can not expand later on to accommodate more and more nested lists that get expanded.
The div that needs to be animated is:
<div className="frame"> [nested ordered lists] </div>
... and the function that is not working is the function named "collapseFrame."
Again, I previously tried using a CSS transition on the max-height property but that is much less than an ideal solution, because it does not work well on unpredictable heights. So, I don't want to do it that way, if at all possible.
I followed a tutorial and got it working with vanilla JavaScript, without React (parts of the code that are commented out), but when translating it to React, I am not sure why I can not get the code to work.
At the moment, the expanding animation is working, but it will stop working if the border of the is removed or changed to the color white. I don't know why. ??
** Also, the collapsing animation is not working at all. The 'transitioned' event does not always fire on the ref object, and sometimes fires after it is supposed to have been removed.
Is anyone able to help point me in the right direction?
This is my codepen.
(I tried to transfer it to JS Fiddle, but it's not working).
https://codepen.io/maiya-public/pen/ZEzoqjW
(another attempt on codepen, for reference. The nested lists are appearing and disappearing, but not transitioning). https://codepen.io/maiya-public/pen/MWgGzBE
And here is the raw code: (I think it is easier to understand on the codepen, but pasting here for good practice).
index.html
<div id="root">
</div>
style.css
.frame {
overflow:hidden;
transition: all 0.5s ease-out;
height:auto;
// for some reason,without the border, not all of them will transition AND it can't be white !??
border: solid purple 1px;
}
button {
margin: 0.25rem;
}
Dummy data: (the nested lists will render based on this object)
let data = {
text: 'list',
children: [
{
text: "groceries",
children: [
{
text: "sandwich",
children: [
{
text: "peanut butter",
children: [{text: 'peanuts', children: [{text: 'nut family'},{text: 'plant', children: [{text: 'earth'}]}] }]
},
{
text: "jelly",
children: [
{ text: "strawberries", children: null },
{ text: "sugar", children: null }
]
}
]
}
]
},
{
text: "flowers",
children: [
{
text: "long stems",
children: [
{
text: "daisies",
children: null
},
{
text: "roses",
children: [
{ text: "pink", children: null },
{ text: "red", children: null }
]
}
]
}
]
}
] };
React code: index.js
// component recursively renders nested lists. Every list item is a list.
const ListItem = ({item, depth}) => {
// depth prop allows me to give a className depending on how deeply it is nested, and do CSS style based on that
let { text, children } = item
let [showChildren, setShowChildren] = React.useState(false)
let frame = React.useRef()
const expandFrame = (frame) => {
//let frameHeight = frame.style.height //was using this at one point, but not anymore b/c not working
// was supposed to have frame.style.height = frameHeight + 'px'
frame.style.height = 'auto'
frame.addEventListener('transitionend', () =>{
frame.removeEventListener('transitionend', arguments.callee)
frame.style.height = null
})
}
const collapseFrame = (frame) => {
let frameHeight = frame.scrollHeight;
// temporarily disable all css transitions
let frameTransition = frame.style.transition;
frame.style.transition = ''
requestAnimationFrame(function() {
frame.style.height = frameHeight + 'px';
frame.style.transition = frameTransition;
requestAnimationFrame(function() {
frame.style.height = 0 + 'px';
})
})
}
return(
<ol>
<button onClick={(e)=>{
if(!showChildren) {
// console.log('children not showing/ expand')
setShowChildren(true)
expandFrame(frame.current)
} else {
// console.log('children showing/ collapse')
setShowChildren(false)
collapseFrame(frame.current)
}
}}
>
{text}-{depth}
{ children && <i className="fas fa-sort-down"></i> || children && <i className="fas fa-sort-up"></i>}
</button>
{/*THIS IS THE ELEMENT BEING ANIMATED:*/}
<div className={`frame depth-${depth}`} ref={frame}>
{showChildren && children && children.map(item => {
return (
<li key={uuid()}>
<ListItem item={item} depth={depth + 1}/>
</li>)
})
}
</div>
</ol>
)
}
class App extends React.Component {
render() {
return (
<div>
<ListItem key={uuid()} item={data} depth={0} />
</div>
);
}
}
function render() {
ReactDOM.render(<App />, document.getElementById("root"));
}
render();