What are hooks in ReactJS?
What are hooks, why do they exist, how to use them, and their history.
Sometimes, the elegant implementation is just a function. Not a method. Not a class. Not a framework. Just a function. — John Carmack. Oculus VR CTO.
When I started to learn React, I didn’t read about its basic concepts. I directly jumped into writing React code because I found it trendy. However, not long into that journey, I faced the consequences of writing code without understanding its benefits.
I finally understood what Ali Abdaal meant when he said, “Learning and Practicing should be balanced together.”
Nonetheless, Hooks were one of the concepts that I had skipped theoretically. I knew how to write them and where to use them, but I wasn’t aware of why we use them.
I understood the intricacies of React Hooks, so you don’t have to. I will help you learn what they are, why and how to use them in this article.
What are Hooks?
By definition, hooks are functions in React that allow us to access and verify specific properties of UI elements, like their current state, without writing classes, constructors, etc.
Hooks allow you to perform operations that Vanilla JS cannot. React has different built-in hooks — State Hooks, Context Hooks, Ref Hooks, Effect Hooks, etc.
You are probably writing a hook in your code when you smash a random word with the “use” keyword and call it a function that deconstructs an array into variables.
Hooks uses features of the React framework, like SSR, to perform a specific task from within a component. However, the hooks do not live inside your React component.
More often than not, these hooks perform basic operations, like trying to check the state of a UI element and whether the user interacted with it.
Hooks allow us to use reusable states of components without changing their hierarchy. With hooks, developers can extract the current state of a UI element, grab DOM elements, and more independently and re-use those states and information in other components without creating extra classes, constructors, props, etc.
We can also create custom hooks, which I will cover in a separate article, and for now, let us look at an example where hooks would be required. Then, we will understand why we need them and how to use them.
E-Commerce Example
Consider yourself running an E-commerce brand. You have a page that displays your product with checkboxes to select the size, colour, etc. By default, the chosen choice is the first choice.
If you want to check whether the user has selected a different choice, you require something directly into your component that verifies the current state of those selection boxes. A hook allows you to do that, specifically, the useState()
hook.
Therefore, the useState()
hook returns the current state of UI elements and performs specific operations accordingly. These actions may include changing the product URL and attaching queries for the user to share.
There are more hooks. If I want to select HTML elements from the DOM, I can use the useRef()
hook. If not using React, I would pick the elements with the querySelector()
method, etc.
React hooks represent those operations that could work manually without a framework, but the complexity of frameworks wouldn’t allow us to continue doing so. Thus, we efficiently implement those basic operations and insert additional benefits.
Of course. Before hooks, developers accessed the state of UI elements and other details, but the downside of those features was writing extra code.
Let’s see why the React team introduced hooks and what developers used before them to access the states of UI elements.
Why use Hooks?
Nowadays, websites aren’t static. There are buttons, checkboxes, text input boxes, crazy animations, etc. A lot is going on at the same time. Developers require various techniques to keep up with these changes in the UI elements and manage them accordingly.
They require various features in their choice of frameworks to swiftly grab user input, such as scrolling behaviour by writing less code. They want to select HTML elements, access their states, receive information from parent elements, etc.
In those cases, we use hooks. Whenever there’s a change, the useState()
hook keeps track of those changes and gets user input. Whenever you want to grab information from parent nodes and pass that information as props to child nodes, you can use useContext()
, and so on.
However, if you think about it deeply, developers had to grab user input even before the React team introduced hooks. React released Hooks in React 16.8 in early 2019.
So, what did developers use before 2019?
At that time, React implemented features in ways that most developers hated. They forced front-end developers to use constructors inside classes with render props for the application to understand the current state of various UI components and use the data provided by the user to perform given actions.
Hooks allowed React developers to eradicate class-based components and directly render the UI with stateful logic and dynamic data from functional components. These functional components were not “dumb components” anymore.
The implementations of React class components were on the pure JavaScript syntax, and its Vanilla implementation inherited the disadvantages of JavaScript classes, including the excessive use of the this
keyword.
React didn’t bother to create a class system either. They said they were not in the business of building a class system and further adopted the default system of JavaScript. All of these problems combined led to the following code.
This is a small example of a class-based code snippet from Fireship trying to increment the count stored in a state wherein the user clicks on the button and displays the current count via an HTML element to the user.
The classes and constructors before early 2019 started to give Java vibes. Back then, only React class components handled states and other DOM-based functionalities. The functional components were merely responsible for displaying information to the user through UI elements.
Functional components till 16.8 received data in the form of props from parent components and precisely displayed them to the user. Developers hated React for that approach. So, React decided to introduce Hooks.
Grabbing data from parent elements, attaining states, and other features were delegated to Hooks.
Oh, and by the way, A state in React keeps track of how data changes in the application. It holds data in different forms that belong to a UI element. For example, it could store user input whenever the user types inside a textbox.
Before Hooks, React was a mess compared to the advancements of today. Classes and constructors led to a wrapper hell. We had to pass a component into another component, and so on.
Developers claimed frameworks provided seamless experience and faster performance.
The entire idea of writing a class with other “yada yada” render props and high-order functionalities to solely check if a user selected a checkbox option contradicted their claim that frameworks were better than plain Vanilla JS.
I wonder why Java developers found themselves inferior. So, the React team decided to step up their game. They introduced hooks that allowed us to access the state of UI elements through functions within React components without being in the same context of a framework.
How to use them?
For starters, you cannot create hooks everywhere. You can only initialize them in the top level of a component. You cannot initiate them inside loops, conditionals, nested functions, etc. Otherwise, you can initialize them inside custom hooks strictly.
import "./App.css";
import { useState } from "react"; // Import the hook
function App() {
const [names, setName] = useState("Afan"); // Initialize the hook
return (
<div className="App">
<p>{names}</p>
<input
type="text"
placeholder="Enter your name"
onChange={(e) => {
setName(e.target.value);
}}
></input>
</div>
);
}
export default App;
I used the useState()
hook in this example. It keeps track of the current state of a UI element. When the state changes, we use the setter function provided by the second deconstructed array element named setName()
to set a new value to the first deconstructed value.
The useState()
hook simultaneously re-renders the UI element whenever the state of an element changes to update the UI elements using the data provided by a hook. The first variable in that hook array stores the data, and the second one is a setter used to collect the information from the UI element.
useState()
solely accepts one optional parameter, which is the default value of the state. In other words, Afan
is the default value of the <p>
tag and other UI elements using that hook to display values. This default value can be in strings, numbers, empty arrays ([])
, empty objects ({})
, etc.
During the days of Classes and Constructors, objects represented the initial default value. Nonetheless, the brackets are required when initializing a hook. Those brackets represent the concept of Array Deconstruction.
However, the variables inside those brackets aren’t arrays. We can use multiple hooks in the same component. Mostly, we use useState()
to keep track of the user input through a form. Forms contain more than one field. Hence, we can initialize multiple hooks with different variable names and initial values at the beginning of the component function itself.
Similarly, you can initialize other hooks. All components using the same hook in the same file are also re-rendered whenever the state of that UI element changes.
Every component that uses the data stored in the state will get re-rendered along with the entire page. React will modify and update those components together. Developers will not manually select each HTML element. Instead, React with its Babel will do that for us to update those components.
Furthermore, hooks are asynchronous. If you try to call them multiple times, they will only re-render the component once in specific scenarios.
import "./App.css";
import { useState } from "react";
function App() {
const [names, setName] = useState("Afan");
function updateP(e) {
setName(e.target.value + "HEY");
setName(e.target.value + "HELLO");
setName(e.target.value + "HOWDY"); // Only this setter function called
}
return (
<div className="App">
<p>{names}</p>
<input
type="text"
placeholder="Enter your name"
onChange={(e) => updateP(e)}
></input>
</div>
);
}
export default App;
I created the updateP()
function inside the main App()
function component and called the hook from that function. I invoked it three times, but only the last one executed and appended HOWDY
to the modified string when it re-rendered.
I earlier claimed that hooks cannot reside inside functions, loops, etc. So, how the heck am I using them here? Well, you can invoke the setter function of the hooks inside functions, but you cannot create hooks inside functions.
You can only create them at the top level of the component function. However, if you want the current string value to get appended with the previous value and invoke the setter function three times synchronously, you can use a callback function inside it.
import "./App.css";
import { useState } from "react";
function App() {
const [count, setCount] = useState(0);
function updateP() {
setCount((prev) => prev + 1);
setCount((prev) => prev + 1);
setCount((prev) => prev + 1);
}
return (
<div className="App">
<p>{count}</p>
<button onClick={updateP}>Increase</button>
</div>
);
}
export default App;
This example uses the default value provided in the hook. When the user clicks the button, the function updateP()
gets invoked, and the count changes.
Since I passed a default value present inside the useState()
hook, those setter functions can use them and keep updating the previous value when I pass them as parameters.
Inside the button, the function call doesn’t require parenthesis. Otherwise, the entire application will enter an infinite loop of function calls. If the parenthesis is attached, the function will get invoked directly before the user clicks the button.
In reality, the hook data lives outside the component that renders it. It is one level outside the component. Even without the respective context, hooks allow us to access that data and restrict its usage if it is confidential since it resides outside the component.
Whenever the state of the component changes, React re-renders the entire page and delivers the data by storing it in the first array element of the useState()
function.
Also, there’s no strict rule about writing the names of the variables deconstructed from hooks. We use the keyword set
for the setter function because it is easier for other developers and ourselves to understand.
Furthermore, we usually define those variables in combination. So, trying to get the first and last name of the user, the hook will have [firstName, setFirstName]
, [lastName, setLastName]
, etc.
It is simply a convention commonly used by React developers while writing hooks to make them easily readable and understandable.
Without Hooks and Constructors
We understand that React recently introduced hooks in early 2019. Before that, developers used Classes and Constructors. Java developers felt inferior.
However, some will argue that we don’t require constructors or hooks to take user input from UI elements and display them. We can do it directly instead. For that, let’s consider a click counter-example.
I will try to directly update the values of a counter by storing them inside a variable, updating them whenever the user interacts with the component, and displaying the updated value to the user.
I will exhibit the counter variable inside the <p>
tag, and whenever I click on the button, it will update the UI element with the new incremented counter.
import "./App.css";
function App() {
let counter = 5;
function updateP() {
counter++;
console.log(counter);
}
return (
<div className="App">
<p>{counter}</p>
<button onClick={updateP}>Increase</button>
</div>
);
}
export default App;
However, when you try to display the new updated value to the user during their interaction with your UI elements, the count variable will get the updated value, not the component.
The counter updates inside the variable, but the UI will not update because we are not using any function forcing React to re-render the component with its new data.
There should be a trigger that refreshes the component and paints the new values.
When using hooks, React updates the variable with the data using the setter function, and developers access the updated data using the first element in the deconstructed array.
They call the hook to grab the data from within the React components.
However, both array elements are useless if React doesn’t re-render the page. When the page re-renders, the new data gets displayed to the user. Since the setter functions in hooks trigger the components to re-render, the UI gets updated too.
And that’s why either hooks or classes are required. Only specific hooks, like useState()
, re-render the page. It is the most commonly used hooks in the majority of applications.
Instead of manually selecting DOM elements, updating the variables of each component that uses the data, and writing that much code, hooks make our lives easier.
Summary
Hooks are functions that developers can invoke or call from within React components. They can access specific information from UI elements, such as user input, grab HTML elements, provide context, etc.
The information attained from UI elements is stored one level outside the React components but within the same exported function.
Hooks allow developers to perform various operations that Vanilla JS will fail to perform. Mostly, we use specific hooks to keep track of the state of a UI element using the useState()
hook.
The useState()
hook allows us to access the details provided by the user or perform actions based on the interactions from the user, like scrolling through an element or clicking a button.
Before hooks, developers used Classes and Constructors. That approach required us to write more code. Therefore, the React team introduced Hooks. We cannot manually reciprocate the functionalities of these hooks, like the useState()
hook.
If we try to replicate the behaviour of the useState()
hook and manually update variables, the UI elements will continue to remain the same and not update because hooks cause React to re-render the entire page while keeping track of the last stored values of UI elements and their states.
Each hook has a unique proposition that solves a problem wherein Vanilla JS fails. Hooks take advantage of the features of the React framework.
And that’s it.
If you want to contribute, comment with your opinion and if I should change anything. I am also available via E-mail at hello@afankhan.com. Otherwise, Twitter (X) is the easiest way to reach out — @justmrkhan.