Answer the question
In order to leave comments, you need to log in
How to make dropdown list with undefined height of child element with animation?
I'm trying to make a drop-down list, but I run into the fact that if the element has an indefinite height, then this cannot be animated. There is a suggestion to use ref.current. clientHeight
. But I don't know how to sync it with animation.
I know that this can be easily solved after reading the documentation, but time is limited.
Link to sandbox: https://codesandbox.io/s/pensive-sutherland-1h9zf?...
import React, { useRef, useState, useEffect } from 'react'
import { useSpring, useTransition, animated } from '@react-spring/web'
const items = [
{ label: 'Hello' },
{ label: 'World', items: [{ label: 'Hello 2 level' }, { label: 'World 2 level' }] },
{ label: 'Man' },
]
const Item: React.FC<any> = ({ label, items }) => {
const [isExpanded, setIsExpanded] = useState(false);
const listWrapper = useRef(null);
const onClick = () => {
setIsExpanded(!isExpanded)
}
const transitions = useTransition(isExpanded, {
from: { opacity: 0, height: '0px' },
enter: { opacity: 1, height: '200px' },
leave: { opacity: 0, height: '0px' },
})
return (
<li>
<a onClick={onClick}>{label}</a>
{transitions(
(styles, isReady) =>
isReady && (
<animated.ul style={styles} ref={listWrapper}>
{items.map(item => (
<li key={item.label}>{item.label}</li>
))}
</animated.ul>
)
)}
</li>
)
}
export default function App() {
return (
<div>
{items.map(item => (
<Item key={item.label} label={item.label} items={item.items} />
))}
</div>
)
}
Answer the question
In order to leave comments, you need to log in
I found a solution by creating a special component that accepts child
index.tsx
// Base
import React from 'react';
import { CSSTransition } from 'react-transition-group';
import useExpandableWrapper from './useExpandableWrapper';
// Assets
import styles from './style.module.scss';
import animation from './animation.module.scss';
interface ExpandableWrapperProps {
isExpanded: boolean;
}
const ExpandableWrapper: React.FC<ExpandableWrapperProps> = ({
children,
isExpanded,
}) => {
const { onEnter, onEntered, onExit, onExiting, state } = useExpandableWrapper();
const { childHeight } = state;
return (
<div style={{ height: childHeight }} className={styles.wrapper}>
<CSSTransition
in={isExpanded}
timeout={600}
classNames={animation}
onEnter={onEnter}
onEntered={onEntered}
onExit={onExit}
onExiting={onExiting}
unmountOnExit
>
{children}
</CSSTransition>
</div>
);
};
export default ExpandableWrapper;
.wrapper {
overflow: hidden;
transition: height 300ms ease;
height: auto;
}
import { useState } from 'react';
const useExpandableWrapper = () => {
const [childHeight, setChildHeight] = useState<string | number>(0);
const onEnter = (element: HTMLElement) => {
const height = element.offsetHeight;
setChildHeight(height);
};
const onEntered = () => {
setChildHeight('auto');
};
const onExit = (element: any) => {
const height = element.offsetHeight;
setChildHeight(height);
};
const onExiting = () => {
const height = 0;
setChildHeight(height);
};
const state = {
childHeight,
};
return {
onEnter,
onEntered,
onExit,
onExiting,
state,
};
};
export default useExpandableWrapper;
.enter {
opacity: 0;
transition: all 600ms cubic-bezier(0.15, 0.84, 0.21, 0.96);
}
.enterActive {
opacity: 1;
}
.exit {
opacity: 1;
transition: all 300ms cubic-bezier(0.15, 0.84, 0.21, 0.96);
}
.exitActive {
opacity: 0;
}
Didn't find what you were looking for?
Ask your questionAsk a Question
731 491 924 answers to any question