visit
If you're delving into modern front-end development, it's impossible to ignore the revolutionary changes that React hooks have brought to the table. Since their introduction in React 16.8, hooks have shifted paradigms, rendering class components almost archaic. No longer do you need to grapple with the complexities of class-based components to access state or lifecycle features; hooks have democratized these capabilities, making them accessible directly within functional components.
But why have hooks become such a game-changer? They excel in simplicity and reusability. Imagine crafting custom logic for a component and then effortlessly applying it elsewhere—without the rigmarole of higher-order components or tricky render props. This "write once, use anywhere" philosophy diminishes code redundancy, enhancing maintainability and scalability.
Let's look into 5 essential hooks for your TypeScript project that will make your life easier.
useBreakpoint
use-breakpoint.ts
import { useState, useEffect, useMemo } from "react";
export const useIsClient = () => {
const [isClient, setIsClient] = useState(() => typeof window !== "undefined");
useEffect(() => {
setIsClient(true);
}, []);
return isClient;
};
export function useBreakpoint() {
const isClient = useIsClient();
const [breakpoints, setBreakpoints] = useState({
isS: false,
isM: false,
isL: false,
});
useEffect(() => {
if (!isClient) return;
const handleResize = () => {
const width = window.innerWidth;
const isS = width <= 348;
const isM = width <= 720;
const isL = width < 1120;
setBreakpoints({ isS, isM, isL });
};
handleResize();
window.addEventListener("resize", handleResize);
return () => {
window.removeEventListener("resize", handleResize);
};
}, [isClient]);
return useMemo(() => breakpoints, [breakpoints]);
}
useClickOutside
use-click-outside.ts
import React, { useEffect } from "react";
export function useClickOutside<T extends HTMLElement>(
refs: React.MutableRefObject<T | null>[],
callback: (event: MouseEvent) => void,
): void {
const handleClick = (e: MouseEvent): void => {
if (!refs.some((ref) => ref.current && ref.current.contains(e.target as Node))) {
return callback(e);
}
};
useEffect(() => {
document.addEventListener("click", handleClick, true);
return () => {
document.removeEventListener("click", handleClick, true);
};
});
}
useHover
use-hover.ts
import * as React from "react";
export function useHover(delay = 0): [React.MutableRefObject<any>, boolean] {
const [value, setValue] = React.useState<boolean>(false);
const ref = React.useRef<HTMLDivElement>(null);
let timer: ReturnType<typeof setTimeout>;
const handleMouseOver = (): void => {
if (timer) clearTimeout(timer);
setValue(true);
};
const handleMouseOut = (): void => {
timer = setTimeout(() => setValue(false), delay);
};
React.useEffect(() => {
const node = ref.current;
if (node) {
node.addEventListener("mouseover", handleMouseOver);
node.addEventListener("mouseout", handleMouseOut);
return () => {
if (timer) clearTimeout(timer);
node.removeEventListener("mouseover", handleMouseOver);
node.removeEventListener("mouseout", handleMouseOut);
};
}
}, [delay]);
return [ref, value];
}
useMobileDetect
use-mobile-detect.ts
import * as React from "react";
export const getMobileDetect = (userAgent: string) => {
const isAndroid = (): boolean => Boolean(/Android/i.exec(userAgent));
const isIos = (): boolean => Boolean(/iPhone|iPad|iPod/i.exec(userAgent));
const isOpera = (): boolean => Boolean(/Opera Mini/i.exec(userAgent));
const isWindows = (): boolean => Boolean(/IEMobile/i.exec(userAgent));
const isSSR = (): boolean => Boolean(/SSR/i.exec(userAgent));
const isMobile = (): boolean => Boolean(isAndroid() || isIos() || isOpera() || isWindows());
const isDesktop = (): boolean => Boolean(!isMobile() && !isSSR());
return {
isMobile,
isDesktop,
isAndroid,
isIos,
isSSR,
};
};
export const useMobileDetect = () => {
const userAgent =
typeof window === "undefined" || typeof window.navigator === "undefined" ? "SSR" : window.navigator.userAgent;
return getMobileDetect(userAgent);
};
useScrollPosition
The useScrollPosition
hook lets you know the current scroll position, which can be useful for implementing "sticky" headers or other UI elements that depend on scroll position.
use-scroll-position.ts
import * as React from "react";
export function useScrollPosition(): [React.MutableRefObject<any>, string] {
const [position, setPosition] = React.useState<"fixed" | "relative">("fixed");
const ref = React.useRef<HTMLDivElement>(null);
const lastScrollY = React.useRef(0);
React.useEffect(() => {
const handleScroll = () => {
const currentScrollY = window.scrollY;
setPosition(currentScrollY < lastScrollY.current ? "fixed" : "relative");
lastScrollY.current = currentScrollY;
};
window.addEventListener("scroll", handleScroll);
return () => {
window.removeEventListener("scroll", handleScroll);
};
}, []);
return [ref, position];
}
As we wrap up, it's clear that React hooks have revolutionized the way we approach modern front-end development. They have not only simplified the transition from class-based components to functional ones but have also provided a clean, reusable, and composable way to manage state and side effects. With hooks, you can say goodbye to cumbersome lifecycle methods and the intricacies of higher-order components. Instead, you get to embrace a streamlined way to share logic across components and build more maintainable, testable, and scalable applications.
Remember, the beauty of hooks lies in their modularity and reusability. They enable you to write concise, focused, and clean code that can be easily tested and debugged. For modern frontend developers, particularly those using TypeScript, these hooks are not just another feature—they are the cornerstone of effective and efficient React development.