Welcome! We want to introduce a new type of articles called 'Quick tips' to show you very simple, but effective solutions that might be helpful in your everyday developer's life. A few days ago, I was working on our client’s website, and to meet the design needs I had to implement a simple accordion behavior. Long time ago, we could use jQuery library to make it real, but today, we can utilize React hooks. I'm going to quickly show you how to do this.
A few lines of code
Let me introduce a simple functional component called Accordion. Here, take a look at this piece of code:
import React, { useState, useRef } from 'react';
import styled from 'styled-components';
const Ul = styled.ul`
height: ${({ height })=> height}px;
opacity: ${({ height })=> height > 0 ? 1 : 0};
overflow: hidden;
transition: .5s;
`;
export const Accordion = ()=> {
const list = ['Boo! 👻', 'Boo! 👻', 'Boo! 👻'];
const content = useRef(null);
const [height, setHeight] = useState(0);
const toggleAccordion = ()=> {
setHeight(height === 0 ? content.current.scrollHeight : 0);
};
return <>
<h2 onClick={toggleAccordion}>Click me</h2>
<Ul height={height} ref={content}>
{list.map((item, index)=> <li key={index}>{item}</li>)}
</Ul>
</>;
};
One thing - for the sake of simplicity, I want to keep HTML and CSS code as clean as possible, so here we have only h2 and ul tags. But I couldn't resist myself from putting a nice opacity and height transition in CSS styles :) Anyway, it's easy to adapt this solution to your specific needs.
As you can see, we are using two React hooks: useRef and useState.
With useRef, we want to keep the reference to an unordered list in DOM tree to obtain some information from it, when useState is being used to store the height of the list which we are going to control. I passed onClick event handler to the h2 tag to call toggleAccordion function every time user will make a click on it. What we also use here is styled-components library which we use in every project. You can read more about this awesome package here.
What exactly is happening here?
Every time user clicks on the 'Click me' heading, we are calling toggleAccordion function which is doing two things:
- reading scrollHeight property from the unordered list - scrollHeight stores the height of an element's content, including content not visible on the screen due to overflow,
- checking current height (which is equal 0 in its initial state), and if it is zero - put scrollHeight, otherwise put 0.
Basically, the height is passed to Ul component and controlled with CSS and styled-components, but you can use inline styles, as well.
Simple thing, right?
But this solution has one serious problem...
Which we are going to solve, of course! This piece of code will work perfectly with static data, but won't work when we dynamically change the list content (the height will stay the same). So let's change the component's code to avoid the problem.
export const Accordion = ()=> {
const content = useRef(null);
const [list, setList] = useState(['Boo! 👻', 'Boo! 👻', 'Boo! 👻']);
const [height, setHeight] = useState(0);
const toggleAccordion = ()=> {
setHeight(height === 0 ? content.current.scrollHeight : 0);
};
useEffect(()=> {
if (height > 0) {
setHeight(content.current.scrollHeight);
}
}, [list]);
const addToList = ()=> {
setList([...list, 'Boo! 👻']);
};
return <>
<h2 onClick={toggleAccordion}>Click me</h2>
<button onClick={addToList}>Add</button>
<Ul height={height} ref={content}>
{list.map((item, index)=> <li key={index}>{item}</li>)}
</Ul>
</>;
};
I made the following changes:
- moved the list to the useState hook
- created a simple addToList function which adds one more element to the list and is fired with onClick event in the newly created button (between heading and unordered list)
- added useEffect hook to react on the list changes - therefore, we can increase the height every time something is added (but only if the list is visible)
And this is it! We’ve created a simple accordion with React hooks, which can be easily adapted to your project.