If you’ve been using React in your development work, you have most likely interacted with a very important React feature - React Hooks. Many years ago, React Hooks did not exist so in today’s email, we’ll be taking a look at what React Hooks are and why we need them.
React Hooks were officially released around Feb 2019 as part of React version 16.8. In summary, Hooks allow us to now use state and other React features without having to write classes. Let’s take a step back and explain what this might mean.
In React, we’re able to create React components which are the building blocks of a React application in one of two ways.
Many years ago, components had to be created with Classes if we ever wanted to keep track of state or use other React-specific features.
This is because keeping track of state or using React lifecycle methods was not possible in React Function components until the emergence of React Hooks.
Here’s an example of a simple <Counter />
Function component that provides functionality for incrementing and decrementing a counter.
import React, { useState } from 'react';
const Counter = () => {
const [count, setCount] = useState(0);
const increment = () => setCount(count + 1);
const decrement = () => setCount(count - 1);
return (
<div>
<p>{count}</p>
<button onClick={increment}>Increment</button>
<button onClick={decrement}>Decrement</button>
</div>
);
};
The above <Counter />
component is fairly straightforward. We create a count state property that’s displayed in the component template. Upon clicking an “Increment” or “Decrement” button in the template, we’re able to increment or decrement the value of count respectively.
What if we wanted to have the same counter functionality in the <Counter />
component in another component we might want to create? We could try and duplicate the counter functionality but that’s not really D.R.Y.
To help us here, we can create a custom Hook that’s responsible for consolidating the functionality around incrementing and decrementing a state property.
Hooks are functions. If we’re interested in creating a new Hook, we can simply create a new function. The correct convention for creating custom Hooks is to have the name of the Hook start with the “use” keyword.
We’ll create a new Hook called useCounter()
that we’ll use to help share the logic we want.
const useCounter = () => {
// ...
}
What would the useCounter()
Hook do? If we take a look at the <Counter />
component we had before, we can try and pinpoint the pieces of code responsible for handling the counter functionality.
We’ll need to have our useCounter()
Hook:
Custom Hooks can call other Hooks. We’ll utilize the useState()
Hook in our useCounter()
Hook to instantiate a state property and create the helper functions we have in mind to change the value of the state property.
import React, { useState } from 'react';
const useCounter = () => {
const [count, setCount] = useState(0);
const increment = () => setCount(count + 1);
const decrement = () => setCount(count - 1);
}
There are two other things we can do to make our useCounter()
Hook more appropriate for use. Instead of initializing the value of the state property in the useCounter()
Hook, we can have the initial value be passed in as an argument. This will help components that use the useCounter()
Hook dictate what the initial value of the state property is to be.
The other thing left for us to do is have our useCounter()
Hook return the values that can be used in our components. We’ll have the useCounter()
Hook return the value of the count
state property being created and the increment()
and decrement()
helper functions.
With these changes, our useCounter()
Hook will now look like the following.
const useCounter = (initialState) => {
const [count, setCount] = useState(initialState);
const increment = () => setCount(count + 1);
const decrement = () => setCount(count - 1);
return [count, { increment, decrement }];
};
useCounter()
returns an array where the first value is the count
state property and the second is an object that contains the helper functions to change the value of count
.
For any component that needs to have this counter functionality, they can now utilize the useCounter()
Hook! Here’s an example of the <Counter />
component we had before now leverage the useCounter()
Hook to achieve the intended outcome.
import React, { useState } from "react";
import ReactDOM from "react-dom";
const useCounter = initialState => {
const [count, setCount] = useState(initialState);
const increment = () => setCount(count + 1);
const decrement = () => setCount(count - 1);
return [count, { increment, decrement }];
};
const Counter = () => {
const [count, { increment, decrement }] = useCounter(0);
return (
<div>
<p>{count}</p>
<button onClick={increment}>Increment</button>
<button onClick={decrement}>Decrement</button>
</div>
);
};
Try it on CodeSandbox here.
Amazing! Custom Hooks help make sharing logic between components easy. Without Hooks and with traditional Class components, sharing logic like what we’ve seen above was only made possible with more complicated patterns like:
Both the Render Props and Higher-Order Component pattern often affected component hierarchy, made code difficult to read, and often lead to “wrapper-hell” where components become surrounded by layers of abstractions. This is the exact motivation of React Hooks - to make sharing stateful logic between components easy.
That’s it for today! See you next week. 🙂
— Hassan (@djirdehh)