Table of contents
Custom hooks are a vital part of React development, offering a means to streamline and enhance coding practices. In this piece, we'll delve into their significance and provide tangible examples of how they revolutionize React development.
Understanding Custom Hooks:
A custom hook in React refers to a JavaScript function that leverages one or more built-in hooks like useState, useEffect, or useContext.
Significance of Custom Hooks:
Custom hooks serve as a mechanism to extract and share logic across React components. They contribute to code cleanliness, organizational efficiency, and overall productivity.
Syntax and Rules for Custom Hooks:
Creating custom hooks in React follows a specific set of rules and syntax guidelines.
All custom hooks should commence with the word "use" to adhere to React conventions. For instance, a custom hook dedicated to managing form input could be named useFormInput.
Custom hooks have the flexibility to utilize built-in hooks such as useState, useEffect, useContext, alongside any custom logic required to encapsulate complex functionalities.
The primary objective of a custom hook is reusability. It should be designed in a manner that allows it to be employed across multiple components within the application, facilitating the abstraction and sharing of common logic.
Custom hooks should strictly handle logic and state management, refraining from containing JSX or causing component rendering. Their purpose is to encapsulate logic rather than define UI elements.
Here's a list of ten useful custom hooks, each accompanied by its respective code example:
0- useFocus Hook:
A bespoke utility designed to bestow focus upon designated DOM elements, facilitating smooth interaction within your application.
import { useRef, useCallback, RefObject } from "react";
export const useFocus = (): [RefObject<HTMLInputElement>, () => void] => {
const ref = useRef<HTMLInputElement>(null);
const focusElement = useCallback(() => {
if (ref.current) {
ref.current.focus();
}
}, []);
return [ref, focusElement];
};
export default function App() {
const [inputRef, focusInput] = useFocus();
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<div>
<input ref={inputRef} type="text" />
<button onClick={focusInput}>Focus Input</button>
</div>
</div>
);
}
1-useDelay Hook:
Harnessing the power of asynchronous execution, this hook introduces controlled delays, enabling graceful transitions and loading mechanisms.
import { useState, useEffect } from "react";
export const useDelay = (delayTime: number): boolean => {
const [done, setDone] = useState<boolean>(false);
useEffect(() => {
const delay = setTimeout(() => {
setDone(true);
}, delayTime);
return () => clearTimeout(delay);
}, [delayTime]);
return done;
};
export default function App() {
const isDone: boolean = useDelay(2000);
return (
<div>
{isDone ? (
<p>Welcome to JavaScript Centric!</p>
) : (
<p>Page is Loading ...</p>
)}
</div>
);
}
2 -useWindowSize Hook:
Empowering your components to adapt dynamically to varying viewport dimensions, this hook lays the foundation for responsive design paradigms.
import { useState, useEffect } from "react";
interface WindowSize {
width: number;
height: number;
}
export const useWindowSize = (): WindowSize => {
const [windowSize, setWindowSize] = useState<WindowSize>({
width: window.innerWidth,
height: window.innerHeight,
});
useEffect(() => {
const handleResize = () => {
setWindowSize({
width: window.innerWidth,
height: window.innerHeight,
});
};
window.addEventListener("resize", handleResize);
return () => {
window.removeEventListener("resize", handleResize);
};
}, []);
return windowSize;
};
export default function App() {
const { width, height } = useWindowSize();
return (
<div>
<p>Window Width: {width}</p>
<p>Window Height: {height}</p>
</div>
);
}
3-useOnClickOutside Hook:
Enhancing user experience by detecting clicks outside specified elements, this hook enables intuitive user interactions and interface behaviours.
import React, { useRef, useEffect, RefObject } from "react";
type EventListener = (event: MouseEvent) => void;
export const useOnClickOutside = (
ref: RefObject<HTMLElement>,
handler: EventListener
): void => {
useEffect(() => {
const listener: EventListener = (event) => {
if (!ref.current || ref.current.contains(event.target as Node)) {
return;
}
handler(event);
};
document.addEventListener("mousedown", listener);
return () => {
document.removeEventListener("mousedown", listener);
};
}, [ref, handler]);
};
export default function App() {
const ref = useRef<HTMLDivElement>(null);
const onClose = () => {
alert("Clicked outside");
};
useOnClickOutside(ref, onClose);
return <div ref={ref}>Click outside this element to close</div>;
}
4-useFormUpdate Hook:
Seamlessly monitor and manage form state changes across your application, ensuring data integrity and user feedback mechanisms.
import React, { useState, useEffect } from "react";
type EventHandler = (event: Event) => void;
export const useFormUpdate = (): (() => void) => {
const [isFormUpdated, setIsFormUpdated] = useState<boolean>(false);
const handleFormUpdate = () => {
setIsFormUpdated(true);
};
useEffect(() => {
const handleBeforeUnload: EventHandler = (event) => {
if (isFormUpdated) {
const message =
"You have unsaved changes. Are you sure you want to leave?";
(event as BeforeUnloadEvent).returnValue = message;
return message;
}
};
window.addEventListener("beforeunload", handleBeforeUnload);
return () => {
window.removeEventListener("beforeunload", handleBeforeUnload);
};
}, [isFormUpdated]);
useEffect(() => {
const formElements = document.querySelectorAll(
"form input, form select, form textarea"
);
const handleFieldChange: EventHandler = () => {
setIsFormUpdated(true);
};
formElements.forEach((element) => {
element.addEventListener("change", handleFieldChange);
});
return () => {
formElements.forEach((element) => {
element.removeEventListener("change", handleFieldChange);
});
};
}, []);
return handleFormUpdate;
};
export default function App() {
const handleFormUpdate = useFormUpdate();
return (
<form>
<input type="text" />
<input type="email" />
<textarea />
<select>
<option value="1">Option 1</option>
<option value="2">Option 2</option>
</select>
<input type="submit" value="Submit" />
</form>
);
}
5-useFetch Hook:
Streamlining data retrieval processes, this hook encapsulates asynchronous fetching logic, paving the way for seamless integration with external APIs.
import React, { useState, useEffect } from "react";
interface FetchResult<T> {
data: T | null;
loading: boolean;
error: Error | null;
}
export const useFetch = <T>(url: string): FetchResult<T> => {
const [data, setData] = useState<T | null>(null);
const [loading, setLoading] = useState<boolean>(true);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
const fetchData = async () => {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error("Network response was not ok");
}
const result: T = await response.json();
setData(result);
} catch (error) {
setError(error);
} finally {
setLoading(false);
}
};
fetchData();
}, [url]);
return { data, loading, error };
};
export default function App() {
const users: FetchResult<any[]> = useFetch<any[]>(
"https://jsonplaceholder.typicode.com/users"
);
return (
<div>
{users.loading ? (
<p>Loading...</p>
) : users.error ? (
<p>Error: {users.error.message}</p>
) : (
<ul>
{users.data?.map((user: any) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
)}
</div>
);
}
6-useDebounce Hook:
Mitigating performance bottlenecks in input handling, this hook introduces debouncing techniques for smoother user interactions.
import React, { useState, useEffect } from "react";
export const useDebounce = <T>(value: T, delay: number): T => {
const [debouncedValue, setDebouncedValue] = useState<T>(value);
useEffect(() => {
const handler = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => {
clearTimeout(handler);
};
}, [value, delay]);
return debouncedValue;
};
export default function App() {
const [inputValue, setInputValue] = useState<string>("");
const debouncedValue: string = useDebounce<string>(inputValue, 500);
const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setInputValue(event.target.value);
};
return (
<div>
<input type="text" value={inputValue} onChange={handleInputChange} />
<p>Debounced value: {debouncedValue}</p>
</div>
);
}
7-useLocalStorage Hook:
Persisting user data across sessions, this hook taps into browser storage mechanisms, ensuring seamless user experiences and data retention.
import React, { useState, useEffect } from "react";
export const useLocalStorage = <T>(
key: string,
initialValue: T
): [T, (value: T | ((val: T) => T)) => void] => {
const [storedValue, setStoredValue] = useState<T>(() => {
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch (error) {
console.error(error);
return initialValue;
}
});
const setValue = (value: T | ((val: T) => T)) => {
try {
const valueToStore = value instanceof Function ? value(storedValue) : value;
setStoredValue(valueToStore);
window.localStorage.setItem(key, JSON.stringify(valueToStore));
} catch (error) {
console.error(error);
}
};
useEffect(() => {
const handleStorageChange = () => {
try {
const item = window.localStorage.getItem(key);
setStoredValue(item ? JSON.parse(item) : initialValue);
} catch (error) {
console.error(error);
}
};
window.addEventListener("storage", handleStorageChange);
return () => {
window.removeEventListener("storage", handleStorageChange);
};
}, [key, initialValue]);
return [storedValue, setValue];
};
export default function App() {
const [token, setToken] = useLocalStorage<string>("tokenName", "");
const handleTokenChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setToken(event.target.value);
};
return (
<div>
<input type="text" value={token} onChange={handleTokenChange} />
</div>
);
}
8-useMediaQuery Hook:
Adapting your application's layout and behaviour based on viewport characteristics, this hook lays the groundwork for responsive and adaptive designs.
import React, { useState, useEffect } from "react";
export const useMediaQuery = (query: string): boolean => {
const [matches, setMatches] = useState<boolean>(false);
useEffect(() => {
const mediaQuery = window.matchMedia(query);
const handleMediaQueryChange = (event: MediaQueryListEvent) => {
setMatches(event.matches);
};
mediaQuery.addEventListener("change", handleMediaQueryChange);
setMatches(mediaQuery.matches);
return () => {
mediaQuery.removeEventListener("change", handleMediaQueryChange);
};
}, [query]);
return matches;
};
export default function App() {
const isMobile: boolean = useMediaQuery("(max-width: 768px)");
return <div>{isMobile ? <p>Mobile View</p> : <p>Desktop View</p>}</div>;
}
9-useToggle Hook:
Offering a simple yet powerful toggle mechanism, this hook empowers components with state-switching capabilities, enhancing user interactivity.
import React, { useState } from "react";
export const useToggle = (defaultValue: boolean): [boolean, () => void] => {
const [value, setValue] = useState<boolean>(defaultValue);
const toggleValue = () => {
setValue((currentValue) => !currentValue);
};
return [value, toggleValue];
};
export default function App() {
const [value, toggleValue] = useToggle(false);
return (
<div>
<div>{value.toString()}</div>
<button onClick={toggleValue}>Toggle</button>
<button onClick={() => toggleValue(true)}>Make True</button>
<button onClick={() => toggleValue(false)}>Make False</button>
</div>
);
}
I hope you enjoyed reading this article. A huge thank you for taking the time to read it.
For more insightful articles, follow me on Hashnode and subscribe to the newsletter for fascinating React content delivered directly to your inbox every Wednesday.
Let's connect on LinkedIn: https://www.linkedin.com/in/ahd-kabeer/
Follow me on Twitter: https://twitter.com/Ahd_Kabeerpi