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
collapsibleContent
height to 'auto' - Using
getComputedStyle
we are calculating the height the content occupies if set to 'auto' - Storing that value in
height
and then setting thecollapsibleContent
height 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>
</>
)
}