React animated accordion from scratch
In this post, I have shown how to build an animated accordion without using any external library. Using getComputedStyle we can get the height of each accordion and animate accordingly with CSS.
This is the functional view of our end product.
This is the basic starting code of the Accordion component
export default Accordion = ({content}) => {
const [accordion, setAccordion] = useState(false)
return (
<>
<div className="accordion">
<header onClick={() => setAccordion(!accordion)}>
<h2>{content.question}</h2>
{/* toggle icon */}
<svg className={accordion ? 'open' : ''} x="0px" y="0px" viewBox="0 0 330 330" width="20" height="20">
<path d="M325.607,79.393c-5.857-5.857-15.355-5.858-21.213,
0.001l-139.39,139.393L25.607,79.393 c-5.857-5.857-15.355-5.858-21.213,
0.001c-5.858,5.858-5.858,15.355,0,21.213l150.004,150c2.813,2.813,6.628,
4.393,10.606,4.393 s7.794-1.581,10.606-4.394l149.996-150C331.465,
94.749,331.465,85.251,325.607,79.393z"/>
</svg>
</header>
<div className="collapsible-content">
<div className="content-wrapper">
<p>{content.answer}</p>
</div>
</div>
</div>
</>
)
}
The state accordion will keep track whether the content is collapsed or not. Initially, it's collapsed. To keep
track of height, we will use another state height and initially set it to 0.
const [height, setHeight] = useState(0)
We will need to add a ref to the div with class name collapsible-content to access it's DOM property.
const collapsibleContent = useRef()
<div ref={collapsibleContent} className="collapsible-content">
<div className="content-wrapper">
...
</div>
</div>
Since we will be animating the height, we need to calculate the height in 'px' when the content size is 'auto'
const toggleCollapsible = () => {
const el = collapsibleContent.current
if (tab) {
el.style.height = 'auto'
const newHeight = getComputedStyle(el).height
setHeight(newHeight)
el.style.height = 0
} else {
setHeight(0)
}
}
When accordion state is true-
- First we are setting the
collapsibleContentheight to 'auto' - Using
getComputedStylewe are calculating the height the content occupies if set to 'auto' - Storing that value in
heightand then setting thecollapsibleContentheight back to 0, because now we will animate from 0 to newHeight.
toggleCollapsible function will be called anytime accordion state is changed.
useEffect(() => {
toggleCollapsible()
}, [accordion])
We will also update collapsibleContent everytime the state of height changes.
useEffect(() => {
collapsibleContent.current.style.height = height
}, [height])
For animation, transition property is added to collapsible-content class
.collapsible-content {
transition: 0.4s height ease-out;
overflow: hidden;
}
Here is the complete code of Accordion component
const Accordion = ({content}) => {
const [accordion, setAccordion] = useState(false)
const collapsibleContent = useRef()
const [height, setHeight] = useState(0)
useEffect(() => {
toggleCollapsible()
}, [accordion])
useEffect(() => {
collapsibleContent.current.style.height = height
}, [height])
const toggleCollapsible = () => {
const el = collapsibleContent.current
if (accordion) {
el.style.height = 'auto'
const newHeight = getComputedStyle(el).height
el.style.height = 0
setHeight(newHeight)
} else {
setHeight(0)
}
}
return (
<>
<div className="accordion">
<header onClick={() => setAccordion(!accordion)}>
<h2>{content.question}</h2>
<svg className={accordion ? 'open' : ''} x="0px" y="0px" viewBox="0 0 330 330" width="15" height="15">
<path d="M325.607,79.393c-5.857-5.857-15.355-5.858-21.213,
0.001l-139.39,139.393L25.607,79.393 c-5.857-5.857-15.355-5.858-21.213,
0.001c-5.858,5.858-5.858,15.355,0,21.213l150.004,150c2.813,2.813,6.628,
4.393,10.606,4.393 s7.794-1.581,10.606-4.394l149.996-150C331.465,
94.749,331.465,85.251,325.607,79.393z"/>
</svg>
</header>
<div ref={collapsibleContent} className="collapsible-content">
<div className="content-wrapper">
<p>{content.answer}</p>
</div>
</div>
</div>
</>
)
}