Custom React Hooks
React is one of the most powerful and popular frameworks being used today, because of its features like declarative syntax, component-based and operator independence.
One of the most awesome features of react is the ability to create custom hooks.
What are Custom hooks?
A custom Hook is a JavaScript function whose name starts with ”use
” and that may call other Hooks.
From the above statement, we can understand that react has given us the abillity to create hooks like a function which can perform specific tasks, even using other hooks inside of it, separate from our main codebase.
Why do we need Custom hooks?
It is similar to thinking, why do we need components? The answer is simple
Custom react hooks offer reusability as when a custom hook is created, it can be reused easily, which makes the code cleaner and reduces the time to write the code(as in the case of component layout). It also enhances the rendering speed of the code as a custom hook does not need to be rendered again and again while rendering the whole code.
Some Examples of custom hooks
some of the custom hooks I created while making projects and they make life much simpler by taking care of tedious tasks behind the scenes.
useAsync
A powerful custom which helps in resolving a promise asynchronously.
import { useCallback, useEffect, useState } from 'react';
// a custom hook to resolve a promise asyncronusly
export default function useAsync(callback, dependencies = []) {
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [value, setValue] = useState();
// whenever our dependencies change, useCallback provides us with a new callbackMemoized
const callbackMemoized = useCallback(() => {
setLoading(true);
setError(undefined);
setValue(undefined);
callback()
.then(setValue)
.catch(setError)
.finally(() => setLoading(false));
}, dependencies);
// our useEffect triggers the callback memoized whenever it changes
useEffect(() => {
callbackMemoized();
}, [callbackMemoized]);
return { loading, error, value };
}
useChangeLogger
A simple react hook that logs the value to the console whenever the value changes.
import { useEffect, useRef } from 'react'
// A simple react hook that logs the value to the console whenever the value chnages.
function useChangeLogger(value) {
const firstRenderRef = useRef(true)
useEffect(() => {
if (firstRenderRef.current) {
firstRenderRef.current = false
return
}
console.log("Value Changed!")
console.log({ value })
// alert("Value Changed! new value: " + value)
}, [value])
}
export default useChangeLogger
useCopyToClipboard
A custom hook that lets you copy text to the clipboard.
import { useState } from 'react';
import copy from 'copy-to-clipboard';
export default function useCopyToClipboard() {
const [value, setValue] = useState();
const [success, setSuccess] = useState();
const copyToClipboard = (text, options) => {
const result = copy(text, options);
if (result) setValue(text);
setSuccess(result);
};
return [copyToClipboard, { value, success }];
}
useDocumentTitle
A simple custom hook that lets to set a document title to a webpage.
import { useEffect } from 'react'
// we are using the useEffect hook in order to set the document title.
// The document title is an important part of any website, because it:
// is displayed in the browser tab
// is also displayed in the browser history
// helps with accessibility and search engine optimization (SEO)
function useDocumentTitle(pageTitle) {
useEffect(() => {
document.title = pageTitle
}, [pageTitle])
}
export default useDocumentTitle
useEventListener
A custom hook used to add an event listener to a custom element.
import { useEffect, useRef } from 'react'
// hook to add a event listener to a target
const useEventListener = (
eventType = '',
listener = () => null,
target = window,
options = null
) => {
const savedListener = useRef();
useEffect(() => {
savedListener.current = listener;
}, [listener]);
useEffect(() => {
if (!target?.addEventListener) return;
// get event listener
const eventListener = event => savedListener.current(event);
// add a event listener to the target
target.addEventListener(eventType, eventListener, options);
// remove the event listener in the cleanup of useEffect
return () => {
target.removeEventListener(eventType, eventListener, options);
};
}, [eventType, target, options]);
};
export default useEventListener;
useForm
A custom hook to handle form events
import { useState } from 'react';
export const useForm = (initialState = {}) => {
const [values, setValues] = useState(initialState);
const reset = () => {
setValues(initialState);
};
const handleInputChange = ({ target }) => {
setValues({
...values,
[target.name]: target.value,
});
};
return [values, handleInputChange, reset];
};
usePrevious
A custom hook is that helps get the previous value of a state variable.
import { useRef } from 'react';
// a custom hook to get the previous value of a state
export default function usePrevious(value) {
// refs for current and the previous value
const currentRef = useRef(value);
const previousRef = useRef(null);
// check if the current value as changed
if (currentRef.current !== value) {
previousRef.current = currentRef.current;
currentRef.current = value;
}
return previousRef.current;
}
useStateWithHistory
Similar to usePrevious hook but better as it stores all the history of a state variable and provides functionality to go to any timeline.
import { useCallback, useRef, useState } from 'react';
const CAPACITY = 10;
// A custom hook which stores prev values of the state (CAPCITY) and provides you
// full control over any of the stored previous state
export default function useStateWithHistory(
defaultValue,
{ capacity = CAPACITY } = {}
) {
// state for currentvalue, refs for history array and pointer to the history array
const [value, setValue] = useState(defaultValue);
const historyRef = useRef([value]);
const pointerRef = useRef(0);
// we modify the set operation
const set = useCallback(
(v) => {
// calculate the value of the current
const resolvedValue = typeof v === 'function' ? v(value) : v;
// if we find that the value has changed
if (historyRef.current[pointerRef.current] !== resolvedValue) {
// delete all instances of the state after the pointer
if (pointerRef.current < historyRef.current.length - 1) {
historyRef.current.splice(pointerRef.current + 1);
}
// push the new resolvedValue to the history array
historyRef.current.push(resolvedValue);
// check for the capacity and shift the least recent value
while (historyRef.current.length > capacity) {
historyRef.current.shift();
}
// set the pointer
pointerRef.current = historyRef.current.length - 1;
}
setValue(resolvedValue);
},
[capacity, value]
);
// utility function to go back in state history
const back = useCallback(() => {
if (pointerRef.current <= 0) return;
pointerRef.current--;
setValue(historyRef.current[pointerRef.current]);
}, []);
// utility function to go forward in state history
const forward = useCallback(() => {
if (pointerRef.current >= historyRef.current.length - 1) return;
pointerRef.current++;
setValue(historyRef.current[pointerRef.current]);
}, []);
// utility dunction to go a specific point / index in history array
const go = useCallback((index) => {
if (index < 0 || index > historyRef.current.length - 1) return;
pointerRef.current = index;
setValue(historyRef.current[pointerRef.current]);
}, []);
return [
value,
set,
{
history: historyRef.current,
pointer: pointerRef.current,
back,
forward,
go,
},
];
}
Conclusion
As you saw above there can be many kinds of custom hooks and some can be as simple as logging a variable to the console. Custom Hooks are very useful in organizing code and keeping bulky, hard-to-read code separate.
This is the link to my GitHub repository with more such custom hooks. Feel free to take a look and enjoy.
If you enjoyed reading Please give a star to the article and the repository
Have an amazing coding day :)
Subscribe to my newsletter
Read articles from Gautam Jain directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Gautam Jain
Gautam Jain
Hi Guys, my name is Gautam Jain and I have been fascinated by the wondrous aspects of technology ever since i was a kid, and after getting my personal computer after 12th standard my interest sky-rocketed. I was so amazed by the cool operations one can perform using such small machine like a laptop and a phone. I begin to dive deep into the concepts of the computer science and to get more proficient in the field, enrolled for a Computer Science undergraduate degree. I graduated with my bachelors degree from GNDEC, Ludhiana in august 2023. Throughout my course of 4 years, I invested in developing a good understanding core computer science concepts like discrete mathematics, Data Structures and Algorithms, Operating systems, Database management, CAM, Cyber Security, Compiler Design etc. Along with my academics, i also invested in developing my problem solving skills by actively indulging in competitive tournaments on platforms like GeeksforGeeks, Leetcode, Code studio, Hacker rank, Code chef and also made a routine of solving daily problem and challenges. I worked together with my team (college friends and work colleagues) to create innovative, scalable and open-source projects and gained experience in software development by working as student SDE intern at E-Cell, TNP Cell and 11 mantras. I am currently working as a Programmer Analyst trainee in Cognizant Technology Solutions, brushing my skills in technologies' like Oracle Apps, Oracle SQL, Oracle PL/SQL, Java, JSP, JDBC, Spring etc. I am always open to opportunities to further polish my skills and get a insight to how the daily world functions with the help of technology. Actively Looking for open technology and project idea discussions.