Getting the Hook

4 minute read

If you have been ignoring the Functional Component aspect of React and only working with Class Components, you might have missed the Hook revolution.

One of the reasons why I focused on Class Components, and payed little attention to Fuctional Components, was the ability to “hook” into the Component Lifecycle. Most of the time I was living in three methods: constructor, componentDidMount and componentWillUnmount methods. Setting up my state in the constructor, making all of my subscription calls in componentDidMount and cleaning everything up in componentWillUnmount.

To me, Fuctional Components were only used for stateless and “pure” components. As soon as one of these components needed to setup a subscription, back it went to the Class Component. I believe there we times I would code the following in my sleep simply due to the number of times it was written:

export default new class extends React.Component {
  constructor(props) {
    super(props)
  }
}

When I found the Hook paradigm, it changed my perspective on everything React related.

Let’s use this

The Hook paradigm exposes a few fuctions that really turn things on their head:

  • useState
  • useEffect

There are a few others that we can get into later but for now, these are the two that you will likely use the most. At least initially any way.

State the Obvious

The useState replaces the need to create your state within the constructor. Before, we typically would create our state in the following manner.

constructor(props) {
  super(props)

  this.state = { active: true }
}

With the useState hook, that changes to the following

const [active, setActive] = useState(true)

It should be fairly easy to gather what is happening in those two code blocks. Both are creating state where “active” will be tracked and maintained across renderings. Both are also setting “active” to a value of “true” initially.

What may not be obvious at first is what is being returned from the useState function. The useState function returns an array where the first item is the the state and the second item is a function use to modify or change the state. Simple.

A few subtle things to cover here. First, I can stop using this.state since the state is not a local variable. You might also miss being able to call a function after the state was updated with the setState function but, you can simple use an effect to handle that instead.

Affect the Effect

The useEffect hook gives us the ability to call functions and methods that produce side effects. Well that clears everything up now doesn’t it? What the hell is a side effect?

A “side effect” is really anything that changes something else (way over simplified). Consider the variable foo. If the value of foo is changed from bar to baz then we have created a “side effect.” In other words, the value of the variable has changed.

If we take a cue from Functional Programming, a function that requires or accesses data outside of its given parameters is consider to have side effects. And while we can debate the validity and practicality of this, it serves to prove our point and show how useEffect can be applied.

const NameBadge = ({ id, onClick }) => {
  const [name, setName] = useState(null)
  useEffect(() => {
    const load = async () => {
      const data = await getNameFromId(id)
      setName(data)
    }

    load()
  }, [id])

  if (!name)
    return null

  return (
    <div className={'badge'}>
      <div onClick={() => onClick(id)}>{name}</div>
    </div>
  )
}

In this example, getNameFromId gets the name from some external data source.

If we look a little deeper into the useEffect method, we see that it has two parameters. The first is the method or function that should be executed. The second is an array but it isn’t really clear what it does.

The second parameter to the useEffect method as a “dependency list” of sorts with a few special rules that help dictate when the given function is executed:

  • If omitted, execute for every update, state or parameter change
  • If an empty array, execute once when the component is mounted
  • Otherwise, execute if any of the given dependencies change

In our example, the id parameter is a dependency so the function will be executed only when the id parameter changes. When it does change the function is called which calls setName which updates the name state and causes the component to re-render.

One thing not shown is the useEffect actually expects a function to be returned. This function is called by React only when the given function is called and after the component has rendered.

Perhaps an example is best served. I am a huge RxJS fan so using subscriptions is the easiest example I can think of:

const NameBadge = ({ id, onClick }) => {
  const [name, setName] = useState(null)
  useEffect(() => {
    const stream$ = getNameFromId(id)
      .subscribe(data => setName(data))

    return () => stream$.unsubscribe()
  }, [id])

  if (!name)
    return null

  return (
    <div className={'badge'}>
      <div onClick={() => onClick(id)}>{name}</div>
    </div>
  )
}

As you can see we return a function that cleans up the subscription by calling unsubscribe.

Hooks Rule

It’s interesting to note that “hooks” have two rules for when and where you can use them. The rules are simple to remember:

  • Only call hooks at the Top Level
  • Only call hooks from React Functions

The first rule makes sense. Basically, don’t call “hooks” within loops or or nested functions. The second rule ensures you don’t try to use “hooks” within the Class paradigm. That’s it.

Next up, we’ll cover one of my favorite things about the new “Hook” paradigm, custom hooks.