React Hooks Essentials

by Nicklas Envall

With Hooks, we "hook into" React features which allow us to use state and lifecycle features without classes. So how do we implement Hooks? The goal of this article to help you quickly get started with just that by giving you a base of the essentials. We'll look at useState, useEffect, and lastly how you can create your own custom hooks.

1. State Hook

The State Hook is what we use when we want to add local state to a function component. To use the State Hook, we must first import useState from React. Then we may declare a state variable by calling useState. useState takes one argument, which is the initial state, allowing us to pass a default value.

That's how we set the state variable, but how do we access it and update it?

Well, after calling useState, it'll return an array which contains two elements, the current state value and a function that can update that value. We utilize array destructing to more easily and cleanly give these values names.

import React, { useState } from 'react';

function Counter() {
    const [count, setCount] = useState(0);
    
    return (
        <div>
            <p>Count is {count}</p>
            <button onClick={() => setCount(count + 1)}>increment</button>
        </div>
    );
}

Here we have created a function component called Counter. We declared a state variable with the initial value 0 by passing 0 to useState. Then by destructing the returned array, we got two variables, count and setCount. Lastly, we can see that a click on the button will invoke setCount, which updates count.

Good to know:

  1. You may use useState more than once.
  2. A state variable can be a string, number, object, array, etc.
  3. The set function replaces the state value, no merging.
  4. Destructing is ES6, not a React feature.

2. Effect Hook

The Effect Hook allows us to perform side effects from a function component. Side effects are something we do after a render, like fetching data. To use the Effect Hook, we must first import useEffect from React. Then in our component, we give useEffect a callback that will be invoked, after a render. We call it "useEffect" because the "effect" is the callback.

import React, { useState, useEffect } from 'react';

function Counter() {
    const [count, setCount] = useState(0);
 
    useEffect(() => console.log(count));
    
    return (
        <div>
            <p>Count is {count}</p>
            <button onClick={() => setCount(count + 1)}>increment</button>
        </div>
    );
}

You can use useEffect more than once, which allows you to separate logic. The useEffects will then be invoked in the same order you declared them in.

useEffect(() => console.log("im first"))
useEffect(() => console.log("im second"))

Conditional useEffect

You may not want to run certain effects after each render, often due to performance reasons. Then can you pass an array as a second argument to useEffect, which tells React to only run the effect if the values within the array have changed.

In the example below, the first effect will only get invoked if width gets updated:

const [count, setCount] = useState(0);
const [width, setWidth] = useState(0);

useEffect(() => {
    console.log('I get run after a render if width has changed')
}, [width])

useEffect(() => console.log('I get run after all renders'))
    
return (
    <div>
        <p>Count is {count}</p>
        <button onClick={() => setCount(count + 1)}>increment</button>
        <button onClick={() => setWidth(width + 1)}>width</button>
    </div>
);

useEffect only on mount

If you only want to use an effect on the mount and unmount, then you pass an empty array.


useEffect(() => {
    console.log("on mount")

    return () => console.log("on unmount");
}, [])

Cleanup with useEffect

You can return a function inside the effect which will get invoked on an unmount and after each render. If you need to clean up anything, you would add that logic inside the returned function because that function will get called before the next effect.

import React, { useState, useEffect } from 'react';

function Counter() {
    const [count, setCount] = useState(0);
    
    useEffect(() => {
        console.log(count);
        
        return () =>  {
            // clean stuff
            console.log('just cleaned!')
        };
    })
    
    return (
        <div>
            <p>Count is {count}</p>
            <button onClick={() => setCount(count + 1)}>increment</button>
        </div>
    );
}

3. Custom Hook

We can create our own hooks which allow us to extract common logic used across our components. Reusing the logic with hooks makes it easier to follow the DRY principle.

You can call other hooks like useState, useEffect, and so on, inside your custom hook. A hook is just a function, so you can return whatever you'd like with your custom hook. You have a lot of freedom, but you should always prefix your custom hook's name with "use" to make it clear that it's a hook.

Custom Hook Example

This code is only for illustration purposes, focus on how we with useValidInput extracted some logic:

// our custom hook
function useValidInput(defaultValue = '') {
  const [value, setValue] = useState(defaultValue);

  if (value.includes('?')) {
    return [value, setValue, false];
  }

  return [value, setValue, true];
}

// component
function UserLogin() {
  const [value, setValue, isValid] = useValidInput();

  return (
    <div>
        <h3>User login</h3>
        {isValid ? <p>good input</p> : <p>bad input</p>}
        <input value={value} onChange={(e) => setValue(e.target.value)}/>
    </div>
  );
}

// component
function AdminLogin() {
  const [value, setValue, isValid] = useValidInput();

  return (
    <div>
        <h3>Admin login</h3>
        {isValid ? <p>good input</p> : <p>bad input</p>}
        <input value={value} onChange={(e) => setValue(e.target.value)}/>
    </div>
  );
}

Review

Creating stateful components that can use React features with functions and hooks instead of ES6 "classes" can bring joy to a lot of JS-developers. But there's more to hooks:

  • Hooks let us add state to our stateless function components without refactoring to classes.
  • Hooks gives us a new way to think about how we handle state in our components.
  • Hooks gives us a more primitive way of sharing stateful logic.

This article only scratches the surface of hooks, there's more to it, but you should now have a good base to start using hooks. But before you do, don't forget to also look at the rules of hooks. If you're wondering about anything, then I'd recommend checking the Hooks FAQ or the docs for hooks.