A Closer Look at useState…and Accordions
In a previous article (link here), I briefly touched on the benefits of using React hooks to allow your Functional components to act more like Class components. In this post, and a subsequent one, I’d like to take a slightly deeper dive into the two most commonly used Hooks, useState and useEffect.
useState
One of the most important reasons why Class components are so popular in React is the ability to hold state. Up until the creation of the hooks system, Functional components were totally unable to compete with that functionality. Now, we can do virtually the same things with Functional components that we can with Class components.
As usual, looking at an example can help visualize this.
I wanted to create an Accordion component, where clicking on the title of a particular element would expand some content.
Most developers would immediately create a Class component to start off with, knowing that this would be their end goal. So let’s start with that.
We will create a class component Dogs
that will accept as props an array of objects, which will each contain a breed name and a breed description. In order for our app to detect whenever a user clicks on a breed, and therefore wishes to expand the description, we will hold a variable called activeIndex
in state…and the activeIndex
will change based on an event handler, triggered by clicking on the name of the breed. The event handler will call on theOnTitleClick
function and within that function, state is changed ONLY by using setState
.
Let’s see how that works by throwing in a console.log
into our onTitleClick
callback and printing out the index number of the breed name we select at the bottom of our page.
Great, that works!
Let’s say, though, that we want to be able to create a Functional component for our Accordion (or that we’ve inherited a Functional component through some legacy code). How can we give that component the same (or at least similar) capabilities? This is where useState
comes in.
The very first thing that must be done is import the useState
function from the React library. Then, within our Functional component, we need to write some code that will initialize a new piece of state, create a function to update that state, and provide state with a starting value.
Using useState
involves implementing array destructuring.
dogs = ["Irish Wolfhound", "Spinone Italiano"]const [firstDog, secondDog] = dogsfirstDog
// "Irish Wolfhound"secondDog
// "Spinone Italiano"
Array destructuring is essentially a shortcut to get references to elements inside an array.
Whenever we call useState
, we get back an array with exactly two elements inside of it. If we did NOT use array destructuring, we would have to write something like:
const According = ({ dogBreeds }) => {
const things = useState(null);
const activeIndex = things[0];
const setActiveIndex = things[1];
That’s just messy and we don’t like messy. So destructuring it is!
Back to useState
. The first argument in the array is the piece of state we are trying to keep track of…this is the value that is going to change over time. The second argument is the function we call to update the piece of state. Calling this setter function causes our entire component to automatically re-render. Finally, useState
takes in one argument, which is the initial value for our piece of state.
Here it is again:
Each of those pieces of useState
has direct equivalents in the syntax of Class components.
Perhaps the most frustrating thing about using useState
is that it’s a little more complicated to define and change multiple pieces of state at the same time.
For example, in a Class component, to initialize multiple pieces of state, we would have:
state = {activeIndex: 0, term: ""}
And then to update state, we would simply do:
this.setState({activeIndex: 10, term: "hello"})
For a Functional component, on the other hand, we would have to call useState
for every piece of state we wanted to keep track of.
const [activeIndex, setActiveIndex] = useState(0);
const [term, setTerm] = useState("")
To update those values, we would then have to call each individual setter function:
setActiveIndex(10);
setTerm("hello")
In our case, we can create an event handler that calls the setter function to set the activeIndex piece of state to the index of the element that was clicked on.
Ok, now armed with this knowledge, let’s finish our accordion using our useState
hook.
If we take a look at our Semantic UI styling, we see that the ‘active’ className controls whether or not an individual item will be expanded. Thanks to our onTitleClick
callback function, whenever a user clicks on a breed, activeIndex
gets updated with the index of whatever was clicked on. So now, when we are iterating over our list of breeds, we need to compare the index that we are currently iterating over to the activeIndex
piece of state. If they are equal to each other, the ‘active’ keyword will be added to the className of the appropriate div
.
We can put together an expression to decide whether to add the ‘active’ keyword to the div className and use this to check whether the index of the element we are currently iterating over is the same as the activeIndex
. If it is, we can assign the keyword ‘active’ to a variable that we can call active
; otherwise, the active
variable can be given the value of an empty string.
const active = index === activeIndex ? 'active' : '';
We can then take that variable and interpolate it into the className of the appropriate div.
<div
className={`title ${active}`}
onClick={() => onTitleClick(index)}
>
<i className='dropdown icon'></i>
{breed.name}
</div><div
className={`content ${active}`}>
<p>{breed.description}</p>
</div>
And, behold, our Accordion!
To sum up useState
:
useState
is an array with two elements (we use ARRAY DESTRUCTURING to reference those elements):
- the first argument is the PIECE OF STATE we want to keep track of
- the second argument is the SETTER FUNCTION we call to UPDATE the piece of state (calling this function will cause our component to re-render)
useState
takes in ONE ARGUMENT, which is the INITIAL VALUE for our piece of state.