React Hooks: Introduction

Intro

In this post we will use the useState hook to track state in a functional component, and use that state to toggle an accordion element.

Hooks are a way to write reusable code, instead of more classic techniques like Inheritance.

Here are a few examples of Hooks.

useState- Function that lets you use state in a functional component.

useEffect- Function that lets you use something like lifecycle methods in a functional component.

useRef- Function that lets you create a ref in a functional component

The hooks built into React can be combined into custom Hooks, which are small chunks of re-usable code.

Scaffolding The Accordion Widget

We are going to make an accordion widget for our first example. Here is the basic scaffold.

The App component has the accordion contents in a simple array that gets passed into the Accordion widget as a prop.

App.js
import React from "react"

import Accordion from "./components/Accordion"

const items = [
  {
    title: "Excepteur sint occaecat",
    content:
      "Morbi tristique senectus et netus et malesuada. Eleifend quam adipiscing vitae proin sagittis nisl rhoncus mattis rhoncus. Morbi tincidunt ornare massa eget egestas purus viverra. Ut tristique et egestas quis ipsum. Nunc faucibus a pellentesque sit amet porttitor eget. Praesent elementum facilisis leo vel fringilla est ullamcorper eget nulla. ",
  },
  {
    title: "Duis aute irure dolor",
    content:
      "Augue interdum velit euismod in pellentesque massa placerat. Id aliquet lectus proin nibh nisl condimentum id venenatis. Non enim praesent elementum facilisis leo vel fringilla. Semper eget duis at tellus at urna condimentum mattis pellentesque. ",
  },
  {
    title: "Ut enim ad minim",
    content:
      "Lacus luctus accumsan tortor posuere ac ut consequat semper. Suscipit adipiscing bibendum est ultricies integer quis auctor. Nunc sed velit dignissim sodales ut. Vestibulum morbi blandit cursus risus at ultrices mi. Netus et malesuada fames ac turpis egestas integer.",
  },
]

export default () => {
  return (
    <div>
      <Accordion items={items} />
    </div>
  )
}

The Accordion widget then maps out the items in the array into some simple divs styled with Semantic UI classes. Note that we have added a click listener on them to console log the index of each item when we click on the title. This is what we will use to toggle the active class.

components/Accordion.js
import React from "react"

const Accordion = ({ items }) => {
  const renderedItems = items.map((item, index) => {
    return (
      <React.Fragment key={item.title}>
        <div
          className="title active"
          onClick={() => console.log("Title clicked", index)}        >
          <i className="dropdown icon"></i>
          {item.title}
        </div>
        <div className="content active">
          <p>{item.content}</p>
        </div>
      </React.Fragment>
    )
  })

  return <div className="ui styled accordion">{renderedItems}</div>
}

export default Accordion

What it looks like currently.

accordion with all expanded

The console logs are working.

console click logs

Breaking the Click Listener into Helper Function

At this point it would be a good practice to break this click listener function out into a helper function instead of putting all of the logic for that inline. Right now it is relatively simple, but it could become more complex, or it could be re-used several times. Let’s break this out into a helper function like so.

components/Accordion.js
import React from "react"

const Accordion = ({ items }) => {
  const onTitleClick = index => {    console.log("Title clicked", index)  }
  const renderedItems = items.map((item, index) => {
    return (
      <React.Fragment key={item.title}>
        <div className="title active" onClick={() => onTitleClick(index)}>
          {" "}
          <i className="dropdown icon"></i>
          {item.title}
        </div>
        <div className="content active">
          <p>{item.content}</p>
        </div>
      </React.Fragment>
    )
  })

  return <div className="ui styled accordion">{renderedItems}</div>
}

export default Accordion

Note that we have passed the index into the onTitleClick() function as an argument to make sure that info passes through to the helper function.

Adding the useState Hook

Now we are going to add our first hook to this component. We start by importing the useState hook from React. Then we define the hook, and down at the bottom we display the current number of the active index, which defaults to null.

components/Accordion.js
import React, { useState } from "react"
const Accordion = ({ items }) => {
  const [activeIndex, setActiveIndex] = useState(null)  const onTitleClick = index => {    setActiveIndex(index)  }
  const renderedItems = items.map((item, index) => {
    return (
      <React.Fragment key={item.title}>
        <div className="title active" onClick={() => onTitleClick(index)}>
          <i className="dropdown icon"></i>
          {item.title}
        </div>
        <div className="content active">
          <p>{item.content}</p>
        </div>
      </React.Fragment>
    )
  })

  return (
    <div className="ui styled accordion">
      {renderedItems}
      <h1>{activeIndex}</h1>    </div>
  )
}

export default Accordion

And at this point as we click the different titles the number on the screen changes. However we completely glossed over what is happening with the actual hook itself.

Explaining the Hook

Let’s go over in detail what we did here

components/Accordion.js
import React, { useState } from "react"

const Accordion = ({ items }) => {
  const [activeIndex, setActiveIndex] = useState(null)  const onTitleClick = index => {    setActiveIndex(index)  }
  const renderedItems = items.map((item, index) => {
    return (
      <React.Fragment key={item.title}>
        <div className="title active" onClick={() => onTitleClick(index)}>
          <i className="dropdown icon"></i>
          {item.title}
        </div>
        <div className="content active">
          <p>{item.content}</p>
        </div>
      </React.Fragment>
    )
  })

  return (
    <div className="ui styled accordion">
      {renderedItems}
      <h1>{activeIndex}</h1>
    </div>
  )
}

export default Accordion

useState is one of the primitive hooks that React gives us access to. It’s entire purpose is to allow you to use state in a functional component, which you may want to do when the state of the component is completely irrelevant to the rest of the application.

The next part is a bit tricky: const [activeIndex, setActiveIndex] = useState(null)

This looks like we are creating an array, but we aren’t. This is actually something called destructuring arrays.

MDN: Destructuring Assignment

What is happening is that the array already exists, it is defined by useState(), and we are assigning variables to the first and second items in that array, and calling them activeIndex and setActiveIndex respectively. This is just a shortcut instead of writing:

const activeIndex = useState[0]
const setActiveIndex = useState[1]

Calling useState always returns an array with two items. We are simply anticipating that and assigning constants to both of those items in the beginning. The names of these constants are not set, we can change these to whatever we want. However the first variable is always the current value of the state, and the second value is the name of the function that we want to call to update the state. If we were keeping track of name is could be [name, setName], colors could be [color, setColor] etc etc.

Knowing this it now makes much more sense inside of our helper function.

const onTitleClick = index => {
  setActiveIndex(index)
}

When we click on the title we are using the setActiveIndex function and passing in index.

Expanding Accordion

In order to make our variables a little bit more semantic I have changed the term index to tab. Now that we have the ability to change the value of activeTab we need to add a checker function inside of our .map loop. If the index of the current item is the same as the activeTab then we want to insert the class “active” into the div.

components/Accordion.js
import React, { useState } from "react";

const Accordion = ({ items }) => {
  const [activeTab, setActiveTab] = useState(null);

  const onTitleClick = (index) => {
    setActiveTab(index);
  };

  const renderedItems = items.map((item, index) => {
    //  if current index is same as active tab, add class "active"
    const activeStatus = index === activeTab ? 'active' : '';
    return (
      <React.Fragment key={item.title}>
        <div className={`title ${activeStatus}`} onClick={() => onTitleClick(index)}>          <i className="dropdown icon"></i>
          {item.title}
        </div>
        <div className={`content ${activeStatus}`}>          <p>{item.content}</p>
        </div>
      </React.Fragment>
    );
  });

  return (
    <div className="ui styled accordion">
      {renderedItems}
    </div>
  );
};

export default Accordion;

accordion functioning

There aren’t any smooth transition animations, and they don’t toggle like you would expect, but if you click them they open and the others close, which is what we wanted. Huzzah.

GitHub Repo

Ncoughlin: React Widgets