A Quick Guide to Understanding React Hooks
Tags: beginners, react, javascript, codenewbie
Published: 2021-06-08 21:21:00

This post will focus on React hooks - specifically useState, useEffect, and useRef. The examples are contrived for the sake of clarity, and don't observe all the typical best practices (like wrapping those emojis in `span` elements 😉). 

----

### React Hooks 🎣

React hooks allow us to use function components to accomplish things that were once only possible in Class components - creating, persisting, and sharing stateful and behavioral logic. Additionally, hooks let us take advantage of certain moments in the component lifecycle. 

> ☝ Strictly speaking, some hooks offer a way to *mimic* lifecycle methods, and are not a 1:1 exchange.

##### 🤔 What *is* a hook? 

Beneath the terminology, and even React itself, a hook is a JavaScript function that follows a pre-defined schema in the form of syntax and expected arguments. 

There are several hooks, each with their own intended purpose and pitfalls - but all hooks follow a couple of rules: 

1. Hooks can only be called from function components or custom hooks (a wide topic for another post!)

2. For React to correctly manage state created with hooks, the order in which they are called must be identical with each re-render. Because of this all hooks must be called in the top level of the component.

> ☝ A function component is just that - a *function*! The top level is the first step, where we might declare a variable or do set up - before conditional tests, looping, or performing actions that could cause mutations, and prior to the return in the bottom level.

In this post we'll be covering the 3 hooks you're most likely to encounter in the wild: **useState**, **useEffect**, and **useRef**. 

---- 

### 1️⃣ The useState Hook 

In JavaScript, Class objects are built in such a way that the sharing of behaviors and values among many instances of themselves is accomplished quite easily, in part because of `this` - a confusing and deep topic of its own. 

On the other hand, functions are scoped. Dumping and re-creating their local variables with each invocation. There is no `prev` or `this`, and persisting values isn't possible without an outside variable.

Function and Class components follow this same idea, which is why function components were commonly known as *stateless* components before the introduction of hooks. Without `this`, or that outside storage, these components were confined to displaying data they had no way to update... Enter the aptly named useState hook. 

Predictably, useState taps into the React's state system - creating a place for function components to add independent slices of state, along with providing a way to update and share them. 

#### Syntax & Use 

![useState syntax](https://i.ibb.co/1ZLbKgc/use-State-Hook.png)

To use any hook, we import it by name directly from React: 

```jsx
// import 
import React, { useState } from 'react'; 

const App = () => {
	
	return (
		<div>
			<p>Give 🐒 some 🍌!</p>
			<button> + 🍌</button>
		</div>
	);
}; 

export default App; 
```

To create a new state variable we'll call the useState function and pass the desired `initial value`, useState's only argument. 

In Class components state is maintained as an object, and new state values are restricted to that format. The state variables created by useState are completely independent of one another, meaning our `intial value` *could* be an object - or a number, a string, an array, and so on. 

We'll create a count with a number:

```jsx
import React, { useState } from 'react'; 

const App = () => {
	// invoke 
	useState(0); 	

	return (
		<div>
			<p>Give 🐒 some 🍌!</p>
			<button> + 🍌</button>
		</div>
	);
}; 

export default App;
```

The useState function returns two things to us - the current state variable with assigned initial value, and a function to update that value. To get them we'll use [array destructuring](https://dev.to/ash_bergs/apb-demystified-destructuring-4gaf). 

> ☝ We can name these values anything we want, but it's convention to use the `varName`/`setVarName` style: 

```jsx
import React, { useState } from 'react'; 

const App = () => {
	// destructure return
const [bananaCount, setBananaCount] = useState(0); 	

	return (
		<div>
			<p>Give 🐒 some 🍌!</p>
			<button> + 🍌</button>
		</div>
	);
}; 

export default App;
```

And just like that - we've created a piece of state that will be persisted between renders. If another slice of state was needed, we could easily create one. There's no hard limit on the amount of times useState can be invoked in a function component. This feature makes it easy to separate concerns and reduce naming conflicts. 

Inside the component we can call and use them directly, no "`this.state`" required: 

```jsx
import React, { useState } from 'react'; 

const App = () => {
	const [bananaCount, setBananaCount] =	useState(0);
	const [appleCount, setAppleCount] = useState(0);

	return (
		<div>
			<p>Give 🐒 some 🍌!</p>
			<p>🍌 : {bananaCount} </p>
			<p>🍎 : {appleCount} </p>
			<button 
				onClick={() => setBananaCount(bananaCount + 1)}> + 🍌</button>
			<button 
				onClick={() => setAppleCount(appleCount + 1)}> + 🍎</button>
		</div>
	);
}; 

export default App;
```

Beyond providing a way to create a new state variable, the useState hook also taps into the lifecycle of a component by triggering a re-render when the setter function is invoked and data is changed. 

----

### 2️⃣ The useEffect Hook

There are a handful of key moments in a component's life that we care about, usually because we'd like to perform some action once they've occurred. These actions might include a network request, turning event listeners on or off, and so on. 

In Class components we do that with the lifecycle methods `componentWillMount`, `componentDidMount`, and `componentWillUnmount`. In function components we can now encapsulate all of this behavior in the useEffect hook and accomplish something *like* lifecycle methods.

> ☝ I remember this hook as "use side effects," because it allows us to *use* certain moments and cause *side effects* when they occur.

#### Syntax & Use 

![useEffect syntax](https://i.ibb.co/1f7GSHb/use-Effect-hook.png)

To use, import from React:

```jsx
// import 
import React, { useEffect, useState } from 'react'; 
// hardcoded data
const data = ["Doug", "Marshall", "Peter"];

const App = () => {
	const [coolDudes, setCoolDudes] = useState(data); 
	
	return (
		<div>Top 🆒 dudes: 
			{coolDudes.map((dude) => (
        <p>😎{dude}</p>
      ))}
		</div>
	);
};

export default App;
```

Right now this component is rendering a list of `coolDudes`, but these are hardcoded values - what if the `coolDudes` ranking was maintained in real-time on a database? Using that, our component could always have the most recent data, and we wouldn't have to update it ourselves. 

Before hooks we would need to convert this component to a Class or move the required logic higher up in the chain. With the useEffect hook we can accomplish this task inside a function component.

To use it, we need to provide two arguments. First a callback function - the "side effect" we want to invoke, and secondly a **dependency array** - telling that callback function when to run.

```jsx
import React, { useEffect, useState } from 'react'; 
// axios fetching library added 
import axios from 'axios';

const App = () => {
	const [coolDudes, setCoolDudes] = useState(data); 
	// invoke hook
	useEffect(() => {
		axios.get('http://superCoolApi/coolDudes')
				.then((response) => {
					setCoolDudes(response.data)
			});
	}, []); 
	
	return (
		<div>Top 🆒 dudes are: 
			{coolDudes.map((dude) => (
        <p>😎{dude}</p>
      ))}
		</div>
	);
};

export default App;
```

> 👆 You'll often see actions like making API calls in a useEffect hook. When reading a component, it can be mentally handy to think of useEffect as an "after render" action. The code inside should not be significant to component structure.

It's important to note that the first argument to useEffect may **not** be asynchronous. This ties back to the rule that all hooks must be called in identical order with each re-render in React. Though the callback function itself may not be asynchronous, we can perform async activity inside of it. 

The example above used a Promise to resolve the API call, but JavaScript `async` and `await` can be used as well:

```jsx
import React, { useEffect, useState } from 'react'; 
import axios from 'axios';

const App = () => {
	const [coolDudes, setCoolDudes] = useState(data); 
	// async fetch 
	useEffect(() => {
		const response = async () => {
			const { coolDudes } = await axios.get('http://superCoolApi/coolDudes')	
		}
		setCoolDudes(coolDudes.data);
			});
	}, []); 
	
	return (
		<div>Top 🆒 dudes are: 
			{coolDudes.map((dude) => (
        <p>😎{dude}</p>
      ))}
		</div>
	);
};

export default App;
```

#### The Dependency Array 

In both of the examples above we passed an empty array as the second argument to the useEffect function. This second argument, known as the dependency array, is the key to telling React *when* the callback function should run.

By using an empty array, an array with one or more values (usually state or props), or omitting the argument completely, we can configure a useEffect hook to run automatically at particular times. 

![dependency array notes](https://i.ibb.co/cbVzWCw/Dependency-Array.png)

#### The Cleanup Function 

Broadly speaking, there are two types of actions performed in a useEffect function - those that require cleanup, and those that don't. So far we've only made a network request, an action that is invoked, returned, stored and forgotten about. It requires no cleanup.

But let's imagine a Search component with a useEffect hook that utilized the JavaScript `setTimeout()` method to wait for a user to stop typing before performing an action. This is a clever and somewhat common pattern to throttle API requests.

Let's take a look at a quick and contrived example: 

```jsx
import React, { useEffect, useState } from 'react'; 
import axios from 'axios'; 

const App = () => {
	// init state 
	const [search, setSearch] = useState("first search term");
	// search state shared with debouncedSearch state 👇
	const [debouncedSearch, setDebouncedSearch] = useState(search); 
	const [results, setResults] = useState([]); 

	useEffect(() => {
		const search = async () => {
			const { data } = await axios.get('http://searchApi.org', {
				// options object to attach URL params 
				// API call is completed with the DEBOUNCED SEARCH 
				// These change depending on the API schema 
				params: {
					action: 'query', 
					search: debouncedSearch
				},
		});
			setResults(data.query.search); 
		}; 
	if (debouncedSearch) search();
	}, [debouncedSearch]); 

	return (
		<React.Fragment>	
			<form>
				<label htmlFor="search">Search</label>
				<input 
					type="search" 
					value={search} 
					onChange={(e) => setSearch(e.target.value}
					placeholder="Search..." />
			</form>	
			<div>
				{results.map(result) => (
					return <div key={result.id}>
						<p>{result.title}</p>
			</div>
		</React.Fragment>
	);
};

export default App; 
```

Right now this component renders a search bar and a list of search result titles. On first render the useEffect will be invoked, performing an API call with the `initial value` we passed to the `search` slice of state and then connected to the `debouncedSearch` state. 

But if a user were to type a new search term nothing would happen. This is because the dependency array is watching the `debouncedSearch` state, and won't fire again until this state is updated. Meanwhile the `input` element is bound to the `search` state via its `value` prop. 

We'll call another instance of the useEffect hook to connect these two separate states and set a timer while we're at it:

```jsx
import React, { useEffect, useState } from 'react'; 
import axios from 'axios'; 

const App = () => {
	const [search, setSearch] = useState("first search term");
	const [debouncedSearch, setDebouncedSearch] = useState(search); 
	const [results, setResults] = useState([]); 

	useEffect(() => {
		const search = async () => {
			const { data } = await axios.get('http://searchApi.org', {
				params: {
					action: 'query', 
					search: debouncedSearch
				}
		});
			setResults(data.query.search); 
		}
	if (debouncedSearch) search(); 
	}, [debouncedSearch]); 

	useEffect(() => {
	// create a timer that must end before calling setDebouncedSearch
		const timerId = setTimeout(() => {
			setDebouncedSearch(search);
		}, 1000);	
	// useEffect can return a cleanup function! 🧼
	return () => {
		// this anonymous function will cleanup the timer in the case that the user keeps typing
		clearTimeout(timerId);
	};
	}, [search]);	

	return (
		<React.Fragment>	
			<form>
				<label htmlFor="search">Search</label>
				<input 
					type="search" 
					value={search} 
					onChange={(e) => setSearch(e.target.value}
					placeholder="Search..." />
			</form>	
			<div>
				{results.map(result) => (
					return <div key={result.id}>
						<p>{result.title}</p>
			</div>
		</React.Fragment>
	);
};

export default App;
```

The second useEffect hook is connected to the search input by its dependency array, watching for changes to the `search` state. When updated, the hook will be invoked and its callback function will then instantiate a timer with the JavaScript `setTimeout()` method.

If we did not cleanup behind this side effect, and the user kept typing, we would run into a problem. Multiple timers would be added to the stack, all waiting 1,000 milliseconds before triggering an API call. This would be a horrible user experience, that's easily avoided by returning the optional cleanup function. 

This function will run right *before* the hook can be executed again, making it a safe place to cancel the last timer before a new one is created with the `clearTimeout()` method.

> ☝ The cleanup function doesn't have to be an anonymous arrow function, though that's generally how you'll see it.

### 3️⃣ The useRef Hook 

The useRef hook is used to attach a reference directly to a DOM node, or to stash a piece of data that we expect to change but whose change we do not want to trigger a costly re-render. The useRef function returns a mutable `ref` object with a single property called `current`. This property will point to whatever we assign the `ref` to.

To get an understanding for how the useRef hook can perform interesting and useful tasks, let's jump right in to a use case. 

#### Syntax & Use 

![useRef syntax](https://i.ibb.co/6whtrgK/React-Hooks.png)

Because it was designed to do a pretty specific job, the useRef hook is seen less frequently than the previous two. But it can be used to facilitate the fluid UI interactions users have come to expect in modern apps. 

For example, when we open a dropdown menu, or toggle the open status of some UI element we usually expect it to close again when: 🅰 We select one of the contained options, or click the element itself. 🅱 We click anywhere else in the document.

Prior to the days of React, when JQuery was more prevalent, this was done by adding an event listener. In React we still add event listeners - either with the `onClick` and `onChange` handlers that come out-of-the-box with React, or by using JavaScript's `addEventListener()` method in a side effect (i.e. a useEffect hook). 

In the following, the example component is rendering a list of articles. When a title is clicked `onArticleSelect` is invoked and the `activeIndex` is reassigned, triggering the `open` status (created in the `renderedArticles` map statement) to change and the details of the article to expand.

```jsx
import React, { useState, useEffect } from "react";

// mock data
const data = [
  {
    id: 1,
    title: "...",
    details:
      "..."
  },
  {
    id: 2,
    title: "...",
    details: "..."
  }
];

export default function App() {
  const [articles] = useState(data);
  const [activeIndex, setActiveIndex] = useState(null);

	// change handler passed to the article element 
  const onArticleSelect = (id) => {
    if (id === activeIndex) setActiveIndex(null);
    else setActiveIndex(id);
  };

  // maps return from articles state
  const renderedArticles = articles.map((article) => {
		// isolate open status by performing a check
    const open = article.id === activeIndex;
    return (
      <article
        key={article.id}
        style={{ border: "1px solid gray" }}
        onClick={() => onArticleSelect(article.id)}
        className="article"
      >
        <h2>{article.title}</h2>
        <div> {open ? <p>{article.details}</p> : null} </div>
      </article>
    );
  });

  return (
    <div className="App">
      <div className="header">
        <h1>🔥Hot Off the Presses🔥</h1>
      </div>
      <section className="articles">{renderedArticles}</section>
    </div>
  );
}
```
The component has some of the functionality we want. The articles expand once clicked, but an article will only close again if: 🅰 It is clicked a second time or 🅱 Another article id is assigned to `activeIndex` state. 

We want to add another layer to this by creating a way for the article to also close if the user clicks on any *other* element in the document. It's not too practical in this small example, but if this component was imported and rendered with many others this could be a quality-of-life improvement in the UI. 

We'll use a useEffect hook to set up an event listener on the `body` element the first time the component is rendered. The listener will detect a click and reset the `activeIndex` to null when triggered:

```jsx
import React, { useState, useEffect } from "react";

const data = [
  {
    id: 1,
    title: "...",
    details:
      "..."
  },
  {
    id: 2,
    title: "...",
    details: "..."
  }
];

export default function App() {
  const [articles] = useState(data);
  const [activeIndex, setActiveIndex] = useState(null);

	// change handler passed to the article element 
  const onArticleSelect = (id) => {
    if (id === activeIndex) setActiveIndex(null);
    else setActiveIndex(id);
  };

  // turns on body event listener
  useEffect(() => {
    const onBodyClick = (e) => {
      // reset the active index
      setActiveIndex(null);
    };
    document.body.addEventListener("click", onBodyClick, { capture: true });
  }, []);

  const renderedArticles = articles.map((article) => {
    const open = article.id === activeIndex;
    return (
      <article
        key={article.id}
        style={{ border: "1px solid gray" }}
        onClick={() => onArticleSelect(article.id)}
        className="article"
      >
        <h2>{article.title}</h2>
        <div> {open ? <p>{article.details}</p> : null} </div>
      </article>
    );
  });

  return (
    <div className="App">
      <div className="header">
        <h1>🔥Hot Off the Presses🔥</h1>
      </div>
      <section className="articles">{renderedArticles}</section>
    </div>
  );
}
```

At first glance this seems like it will work - but there's a problem. When the title is clicked a second time it no longer toggles the display. This has to do with a programming principle known as **event bubbling,** and the way in which the React event system sits on top of that. 

In short, the click events we assigned to the `body` and the `article` element go through a process of reconciliation. During that process events bubble up from the *most parent* element, and the events bound with `addEventListener()` will always be called before the event listeners we attach through React's `onClick` prop. 

When the title is clicked a second time the event listener in the useEffect fires first, setting the `activeIndex` to null, before the `onClick` handler fires immediately after, setting the `activeIndex` *back* to the original index we were trying to dump. 

To solve this we need a way to tell React when a user is clicking inside an `article` element and when they are clicking anywhere else. To do that, we'll employ the useRef function. 

After importing the hook from React, we'll instantiate the `ref` as empty in the top level of the component. 

> ☝ It's best practice to simply invoke the hook in the top level, saving the task of assigning it for the component return, or through a side effect, perhaps a useEffect hook. The rendering phase in a React function component should be 'pure' - meaning it should cause no side effects, and updating a `ref` **is** a side effect.

```jsx
import React, { useState, useEffect, useRef } from "react";

const data = [
  {
    id: 1,
    title: "...",
    details:
      "..."
  },
  {
    id: 2,
    title: "...",
    details: "..."
  }
];

export default function App() {
  const [articles] = useState(data);
  const [activeIndex, setActiveIndex] = useState(null);
  const ref = useRef();

  const onArticleSelect = (id) => {
    if (id === activeIndex) setActiveIndex(null);
    else setActiveIndex(id);
  };

  useEffect(() => {
    const onBodyClick = (e) => {
      // adds a check: did the event occur in the ref node?
      if (ref.current.contains(e.target)) {
				// if yes, return early
        return;
      }
      setActiveIndex(null);
    };
    document.body.addEventListener("click", onBodyClick, { capture: true });

    // removes the event listener, should articles unmount 🧼
    return () => {
      document.body.removeEventListener("click", onBodyClick, {
        capture: true
      });
    };
  }, []);

  const renderedArticles = articles.map((article) => {
    const open = article.id === activeIndex;
    return (
      <article
        key={article.id}
        style={{ border: "1px solid gray" }}
        onClick={() => onArticleSelect(article.id)}
        className="article"
      >
        <h2>{article.title}</h2>
        <div> {open ? <p>{article.details}</p> : null} </div>
      </article>
    );
  });

  return (
    <div className="App">
      <div className="header">
        <h1>🔥Hot Off the Presses🔥</h1>
      </div>
      <section ref={ref} className="articles">
        {renderedArticles}
      </section>
    </div>
  );
}
```

We attached the `ref` to the *most parent* element of the `article` elements, in this case that's the `section` with class name "articles". 

The useEffect hook was also updated to perform a check - depending on the results of that check the `body` event listener will either return early, performing no function and allowing the `onClick` handlers to do their work unhindered, or it will execute and reset the `activeIndex` once more. 

> ☝ The `contains()` method is available to all DOM elements - which is exactly what `ref.current` points to in this instance.

----- 

The introduction of hooks created a shift in the React ecosystem, allowing the once stateless function component to take on huge levels of complexity and functionality. While hooks don't offer a 1:1 trade-off from the lifecycle methods found in Class components, they allow us to create highly reusable, testable, and maintainable components and pieces of state. 

The hooks covered here are only part of the story, and a complete list can be found in the [Official React Docs](https://reactjs.org/docs/getting-started.html). 

### Resources:

- [Advanced Web Development with React](https://www.google.com/books/edition/Advanced_Web_Development_with_React/0MTSDwAAQBAJ?hl=en&gbpv=0) - Mehul Mohan, pdf 📕
- [Modern React with Redux](https://www.udemy.com/course/react-redux/) - Stephen Grider, udemy 🏛
- [React useRef Hook](https://medium.com/trabe/react-useref-hook-b6c9d39e2022) - Ceci García García, medium.com
- [Storing Data in State vs. Class Variable](https://www.seanmcp.com/articles/storing-data-in-state-vs-class-variable/) - seanmcp.com
- [A Thoughtful Way to Use React's useRef Hook](https://www.smashingmagazine.com/2020/11/react-useref-hook/) - Aleem Isiaka, Smashing Magazine

##### 🦄 As always - thank you for reading! 🕶