Codesmith Blog

What are Hooks in Programming?

Written by Maximilian Gonzalez | Sep 26, 2019 7:59:00 PM

A “Hook” is one of those software engineering terms without a clear or precise definition. TechTalk writer PC Matic Malware Research tells us, “the term hooking covers a range of techniques used to alter or augment the behavior of an operating system, of applications, or of other software components by intercepting function calls or messages or events passed between software components.” This is a pretty vague definition. Broad terms like “range of techniques” don’t tell us a whole lot, and other definitions you’ll find on the internet don’t give us much more.

Nonetheless, the concept of a hook in programming pops up in a variety of libraries and software across the stack. React on the front end, Mongoose on the back end, and even Git on the DevOps side. These are all very different tools, so it might be difficult to see how they could possibly relate, but their “hooks” all have something in common. Let’s look at a couple of examples and see if we can get a handle on what a “hook” really is.

React Hooks: Enhancing Component Behavior

Let’s start by looking at what are arguably the most prominent hooks for web developers at the moment; React hooks (if you don’t know React, here’s a great place to get started).

Ever since the February 2019 release of React 16.8, React Hooks have been all the rage, showcasing the importance of hooks in programming. The internet is practically flooded with tutorials about how to use them, and opinions about whether or not you should use them. “Hooks” may sound like a strange term if you’ve never heard it before. The React docs justify this wording by explaining that hooks are “functions that let you ‘hook into’ React state and lifecycle features from functional components.” This kind of makes sense, but it’s still a bit vague about what it really means for a function to “hook into” something.

Basically, in React, hooks are functions that you can put in your components that can do a lot of powerful things, such as managing component state and controlling a component’s lifecycle (mounting, updating, and unmounting). The most commonly used hooks are useState and useEffect.

The useState hook manages the storage, persistence, and updating of data that is relevant to a component (put simply, its state).

const [friends, setFriends] = useState([]);

This one line of code is powerful. It creates a piece of state (let’s say we’re making a social media site. Here, we’re initializing a variable called friends as an empty array) and a function you can use to update the count variable that we call setFriends. Using it allows React to performantly rerender your component with updates to its state.

useEffect lets you control how your component behaves throughout its lifecycle (mounting, updating, and unmounting).

useEffect(() => {

fetch(‘/friends’)

then(res => res.json())

.then(friends => setFriends(friends));

}, []);

This call to useEffect will fetch a list of friends and update the friends array we declared earlier when a component mounts for the first time. The call to setFriends will trigger a rerender.

It’s important to understand that React could do all of these things before the 16.8 update that added hooks. But before the update, you needed to write your components as classes. Functional components were far less powerful and were commonly referred to as “stateless components”. State could only be stored in an object initialized in a class component’s constructor method, and lifecycle had to be managed by overwriting predefined methods on the React.Component class.

But in JavaScript, classes can get confusing, and class based components can get messy. The hooks included in React 16.8 allow you to manage state and lifecycle within a component written as a function, instead of a class. Or, as the docs say, they let your functional components “hook into” state and lifecycle features.

Does this mean that class components also “hook into” state and lifecycle?

It would appear so. Even before the hooks in version 16.8 were announced, people were referring to the functions that control the lifecycle of a class component as “lifecycle hooks”.

Interesting.

Even though the React docs don’t refer to them this way, a lot of other people across the internet called the lifecycle methods for class components “hooks” too.

componentDidMount() {

fetch(‘/friends’)

.then(res => res.json())

.then(friends => this.setState({friends}));

}

This code, implementing the componentDidMount lifecycle method, does exactly what the above call to useEffect does (assuming we initialized state in a constructor), just in a class component, the only way you could do this kind of thing pre-hooks.

And to be fair, the lifecycle methods for classes and the hooks for functional components both fit PC Matic’s definition of a “hook” equally well. The React devs may have chosen this name for the new update just to be catchy (no pun intended, but I’m rolling with it anyway), but it might be accurate to describe these older lifecycle methods as “hooks” too.

Both React hooks for functional components and “lifecycle hooks” for classes “augment the behavior of an application” by “intercepting function calls”. In short, you’re injecting your own code into the flow of React.

By default, when a component mounts, it simply renders whatever HTML you’ve told it to render. If it updates, it just renders that HTML again, changing depending on whatever piece of the component’s state has changed. If it unmounts, it just disappears. This is all handled by code written by the React team. With hooks (what the React team calls “hooks”, and the class methods that other people have called “lifecycle hooks”), you’re augmenting this behavior. You can “intercept function calls” because when you write code using useEffect or componentDidMount or any of the other methods I’ve mentioned, your code will run first, then whatever code React runs by default will run.

Custom code running between prewritten pieces of code? This is beginning to sound a lot like middleware. “Middleware”, like “hooks”, is another software engineering term with a lot of different definitions floating around, many of which are quite vague. For example, in the Node.js library Express, “middleware” is any custom code you want to run between your server receiving a request and sending a response. Your middleware can read and alter both the request and response. This sounds like similar behavior.

So what exactly is the difference between middleware and hooks?

Mongoose Hooks: Executing Custom Code

Well, that’s where this gets a little annoying. Let’s look at Mongoose, a JavaScript library that lets you easily work with MongoDB, a popular NoSQL database. Mongoose also has some features called hooks. But let’s see what the Mongoose docs say about them.

“Middleware (also called pre and post hooks) are functions which are passed control during execution of asynchronous functions.”

“Middleware… also called… hooks.”

I wish I could tell you the difference between these terms. But when docs for a popular JavaScript library treat the two terms like synonyms, we get a little stuck.

It’s important to note that while Mongoose refers to these functions as both "middleware" and "hooks," they are conceptually similar to React hooks and lifecycle methods. Just as React hooks allow you to augment the component lifecycle, Mongoose hooks let you inject custom logic at different stages of the MongoDB query lifecycle. This dual terminology might be confusing at first, but the core idea is the same: modifying the behavior of pre-existing code during execution.

Let’s take a closer look at what Mongoose hooks (or middleware) do, and maybe that’ll help clear things up. There seem to be two main kinds of Mongoose hook methods: “post” and “pre” hooks.

var schema = new Schema();

schema.pre(‘save’, function(next) {

console.log(‘right about to save to the database’);

next();

});

Using a pre hook and passing in the string ‘save’, the provided callback function will run before anything is saved into your database.

schema.post(‘remove’, function(doc) {

console.log(‘%s has been removed’, doc._id);

});

Using a post hook and passing in the string remove will run the provided callback after an entry has been removed from your database.

There are many ways to combine pre and post hooks with parts of the MongoDB lifecycle, but here’s what they all have in common - these hooks (or middleware) allow you to inject your own logic into various parts of that lifecycle. Before something is saved, after something is saved, before an entry is found, or after an entry is updated, etc, you can use these functions to run your own custom code.

It’s middleware because it runs “in the middle of” other prewritten code in the Mongoose library. But these functions are also very similar to React hooks and lifecycle methods, and fit the definition of a “hook” very well too. Much like in React, Mongoose hooks allow you to augment the lifecycle of a database query with your own custom logic. While Mongoose doesn’t use the term “lifecycle” as explicitly as React, we can still think of searching for data, optionally mutating data, and returning data as the lifecycle of a MongoDB query. Hooks allow you to “intercept function calls” within this lifecycle by letting you inject custom logic to augment the default flow of a query.

The similarities are clear. Without the confusion with “middleware,” I’d say the concept of a “hook” is starting to make a lot of sense.

Git Hooks: Automating Git Workflow

Let’s look at one final example. Git, a popular software for version control, a vital part of the development process, no matter what language you’re developing in, has hooks too. Git hooks are not part of a JavaScript library; they are customizable shell scripts that allow for executing custom code at specific points in the Git lifecycle. You can inject your own custom code into these hooks so that certain things will happen, for example, before you commit your code, before you push your code to a remote repository, or after your code gets merged into a repository. Git hooks can be used in all kinds of useful ways, for example, emailing team members to notify them about code being merged, or making sure committed code passes tests before it can be pushed to a remote repository.

So...What Are Hooks?

You can spend a long time online searching for a meaningful definition of the term “hook” (and, for that matter “middleware”). But let’s think about what these three examples of hooks have in common so we can come up with a definition that makes sense.

How are Git hooks similar to React hooks and Mongoose hooks? They all let you augment the default performance of a system by injecting your own logic into certain places in that system’s lifecycle.

Perhaps a hook is a specific type of middleware meant to target a certain point in the lifecycle of a system? These terms are all somewhat nebulous, but I think this could be a valuable and sensible way of looking at them.