Introduction To Custom React Hooks
An introduction to the recently introduced Custom Hooks by React and how they can be used for an elevated DevOps experience
Although introduced quite recently, Hooks have fit into React development processes seamlessly. They are functions that "hook" into existing components or modules to provide us with logical and reusable features. In the terms of React, they are built-in functions that allow developers to use state and lifecycle methods inside functional components.
Some of the most common Hooks provided by React are useEffect
, useState
, useContext
, useRef
, useMemo
, etc. Let's take a look at a few of them and see how can they fit into our components seamlessly.
React Hooks
useState
Hook
This Hook allows us to set the state in functional components by returning an array of two elements: the first being the state variable and the second being the function to modify it. We can also initialize our state value by sending an argument in the Hook call.
useEffect
Hook
This Hook adds the ability to perform side effects in a functional component which includes operations like data fetching, attaching and detaching event listeners, etc.
The following code is an example of you can set the document title when we try to increase the count by clicking on a button:
function Example() {
const [count, setCount] = useState(0);
// Similar to componentDidMount and componentDidUpdate:
useEffect(() => {
// Update the document title using the browser API
document.title = `You clicked ${count} times`;
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
By default, React runs a "side effect" after every render but we can modify it according to our own needs. This is possible with the help of a dependency array. If our variables added in the dependency array change, only then will the side effect runs.
Custom Hooks
The React community has done a great job in providing us with Hooks that cover almost any logic we can think of. However, there are cases wherein you can't implement them on their own because multiple components may have similar logic.
Let's say we have an application that has different components which can edit a user's information in which there is one component in the Setting's section and the other in the Home section. We would have the same functions for handling the API call and updating the state for both of them. What about the details of user which aren't shown in either of these sections and are still editable? You can use the same API to edit those details.
This is just one entity and it has already led us to write the same piece of code at three places in our application. Here is where Custom Hooks come in to save the day! A Custom Hook by definition is a JavaScript function whose name starts with use
and this component will call other React Hooks to extract logic from multiple components and make them reusable.
Some use cases for them are:
Form validation:
Let's take two simple input fields: Email and Password.
- These will be initialized along with their validation boolean states in the
useValidation
. The Hook consists of auseEffect
which is what one that handles the validation process and each time the email or password is changed, the side effect will run. Thevalue
andonChange
properties are being handled by the Hook; this way, other components can reuse this logic to validate their authentication forms along with their fields.
- Instead of making specific state variables, you can also send parameters to the Hook stating the number of input fields you want, their names and types. This may need some additional conditions and tinkering alongwith the logic for validation but it surely becomes more reusable. Refer to the code given below to proceed:
const useValidation = () => {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [isEmailValid, setIsEmailValid] = useState(false);
const [isPasswordValid, setIsPasswordValid] = useState(false);
const emailRegex =
/^([a-zA-Z0-9_\-\.]+)@([a-zA-Z0-9_\-\.]+)\.([a-zA-Z]{2,5})$/;
const passwordRegex =
/^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$%^&*-]).{8,}$/;
useEffect(() => {
setEmail(email);
setPassword(password);
if (email.match(emailRegex)) {
setIsEmailValid(true);
} else {
setIsEmailValid(false);
}
if (password.match(passwordRegex)) {
setIsPasswordValid(true);
} else {
setIsPasswordValid(false);
}
}, [email, password]);
return {
isEmailValid,
isPasswordValid,
email,
password,
setEmail,
setPassword,
};
};
//component
export default function App() {
//calling the hook
const {
isEmailValid,
isPasswordValid,
email,
password,
setEmail,
setPassword,
} = useValidation();
const handleSubmit = () => {
//code for submitting code
};
return (
<div className='App'>
<h2>Email</h2>
<input value={email} onChange={(e) => setEmail(e.target.value)} />
<span>{isEmailValid ? "Valid" : "Invalid"}</span>
<h2>Pasword</h2>
<input
value={password}
type='password'
onChange={(e) => setPassword(e.target.value)}
autoComplete={false}
/>
<span>{isPasswordValid ? "Valid" : "Invalid"}</span>
<button
type='submit'
onClick={handleSubmit}
disabled={!(isEmailValid && isPasswordValid)}
autoComplete={false}
>
Submit
</button>
</div>
);
}
I wrote the code snipped given above with complete logic so that you can understand that these Hooks are really simple. You don't have to worry about the complexity of the code, our next few examples would only help us with the code flow rather than with the functionality.
So, besides field validation what other component logics are very common in React?
Fetching data/ making an API call
In this example, we're going to create a Hook which will make an API call for us. It will have three state variables: loading, data and error. These would be updated accordingly when we call this Hook in the required component and send a url
as an argument. This way any component can make an API call using the same Hook and we wouldn't have to worry about redundancy. Refer to the code given below:
const useFetch = (url) => {
const [loading, setLoading] = useState(true);
const [data, setData] = useState(null);
const [error, setError] = useState(null);
const fetchApi = (url) => {
fetch(url)
.then((response) => {
return response.json();
})
.then((json) => {
setData(json);
})
.catch(() => {
setError(error);
})
.finally(() => {
setLoading(false);
});
};
useEffect(() => {
fetchApi();
}, [url]);
return { loading, data, error };
};
//component
export default function App() {
//calling the hook
const { data, loading, error } = useFetch("https://api-call");
if (loading) return <Loading />;
return (
<div className='App'>
{data ? JSON.stringify(data) : JSON.stringify(error)}
</div>
);
}
Authentication
Our Custom Hook, useAuth
consists of authentication methods like signin
, signup
, and signout
. Additionally we can add methods for changing the password, forgot password functionality etc. The crux is that the user
state in our Hook would store the user's details and would send it to other components along with the required authentication methods. This way you can call these functions from anywhere in the application and we are also provided with an an Event Listener in the useEffect
for re-authentication. Refer to the code given below:
const useAuth = () => {
const [user, setUser] = useState(null);
const signin = (email, password) => {
// code for signing and setting the user
};
const signup = (email, password) => {
// code for registering an account and setting the user
};
const signout = () => {
// code for signing out and setting the user
};
return {
user,
signin,
signup,
signout,
};
};
export default function App() {
const { signin, user } = useAuth();
if (!user) {
signin();
}
return <div>{user}</div>;
}
Pro Tips
These are some of the best practices you can follow which would help you make pro level Custom Hooks:
- First and foremost, follow the rules of Hooks.
- The React team has built an ESList plugin called eslint-plugin-react-hooks for helping developers write Custom Hooks efficiently.
- Follow the React component structure diligently in order of state, function and render.
- Use
memoization
anduseCallback
Hooks for reducing operational time.
To sum it up...
These are just few use cases which we have seen in detail. Besides these, you can create them for a variety of use cases. Some of these include:
useToggle
for storing logic of aboolean
state valueuseRouter
for handling all routing related logicuseEventListener
for attaching event listeners to componentsuseDarkMode
for adding themes to your application which can be accessed from anywhere in the applicationuseAnimation
for animation related methodsuseHistory
if you want to store a history of actions in the applicationuseLocalStorage
for handling actions regarding storage
As you can see, one can find multiple use cases for Custom Hooks. The ones mentioned above are some of the most common ones. You can also find more use cases for them on the internet or create your own Hooks!