A Closer Look at useState…and Accordions

Photo by David Vilches on Unsplash

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.

A lot of my apps are dog themed, yes.

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.

If we click on “Irish Wolfhound”, which is at index 2 in our array of breeds…we see our console.log working and we see the correct index number being printed 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.

How to set up useState

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!

Yay, our Accordion works! Click on a breed, and you expand its description. Only the description of the breed clicked on will be expanded.

To sum up useState:

  • useState is an array with two elements (we use ARRAY DESTRUCTURING to reference those elements):
  1. the first argument is the PIECE OF STATE we want to keep track of
  2. 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.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store