useReact: My go-to React Hooks

· 2 minutes

Last updated:


WIP: This post is WIP. I plan to continue to update it as my React journey continues.

Two years ago, there was a hype around React; and after a couple of months of trying to avoid yet-another-javascript-framework, I gave up and decided to see what all the hype was all about. Since that time, Ive not looked back.

Fast-forward to today, I've used React (specifically with Next.js) to create over 4 professional sites, and over 7 side projects. And throughout that time I have created a few custom hooks that I found myself reusing across projects. These hooks help simplify my flow of data, and give me finer control over my components. These hooks may not be applicable to all types of applications, but for the most part they have been super useful for my use-cases:

UseAsync

I use this hook to control data flow from async methods. This is usually from using third-party libraries, but can be useful in other cases:

import {useCallback, useEffect, useState} from "react";

export default function useAsync<T>(method: () => Promise<T>, dependencies:Array<any> = []) {
	const methodCaller = useCallback(()=>{
		setError(null);
		setStatus("pending");
		method().then(response => {
			setData(response);
			setStatus("fulfilled");
		}).catch(err => {
			setError(err);
			setStatus("rejected");
		});
	}, [method, ...dependencies]);
	const [data, setData] = useState<T>(null);
	const [status, setStatus] = useState<"fulfilled" | "pending" | "rejected">("pending");
	const [error, setError] = useState<Error>(null);
	
	return {status, data, error, run:methodCaller};
}

// USAGE
function MyComponent(){
	const {data, run, status} = useAsync(()=> fetchMovieDetail('harry potter'));

	return (
		<div>
			<pre>{status === 'fulfilled' && JSON.stringify(data, null, '\t')}</pre>
			<button onClick={run}>{status === 'pending' ? 'Pending' : 'Fetch'}</button>
		</div>
	)
}

UseRender

Occasionally, I find myself needing to interface with third-party libraries and need to control the rendering cycle manually. React is good at managing rendering cycles in most cases, but in those rare situations like when you are using third-parties like Monaco, this may come in handy:

import {function useReducer<S, A extends React.AnyActionArg>(reducer: (prevState: S, ...args: A) => S, initialState: S): [S, React.ActionDispatch<A>] (+2 overloads)
An alternative to `useState`. `useReducer` is usually preferable to `useState` when you have complex state logic that involves multiple sub-values. It also lets you optimize performance for components that trigger deep updates because you can pass `dispatch` down instead of callbacks.
@version16.8.0@see{@link https://react.dev/reference/react/useReducer}
useReducer
} from "react";
export default function
function useRender(): {
    render: React.ActionDispatch<[]>;
}
useRender
() {
const [const count: numbercount, const increment: React.ActionDispatch<[]>increment] = useReducer<number, []>(reducer: (prevState: number) => number, initialState: number): [number, React.ActionDispatch<[]>] (+2 overloads)
An alternative to `useState`. `useReducer` is usually preferable to `useState` when you have complex state logic that involves multiple sub-values. It also lets you optimize performance for components that trigger deep updates because you can pass `dispatch` down instead of callbacks.
@version16.8.0@see{@link https://react.dev/reference/react/useReducer}
useReducer
(n: numbern => n: numbern + 1, 0);
return {render: React.ActionDispatch<[]>render:const increment: React.ActionDispatch<[]>increment} }

UseEvent

While working on a previous side project, I found myself adding event listeners to dom events on a regular basis, so I ended up with the following:

import {function useCallback<T extends Function>(callback: T, deps: React.DependencyList): T
`useCallback` will return a memoized version of the callback that only changes if one of the `inputs` has changed.
@version16.8.0@see{@link https://react.dev/reference/react/useCallback}
useCallback
, function useEffect(effect: React.EffectCallback, deps?: React.DependencyList): void
Accepts a function that contains imperative, possibly effectful code.
@parameffect Imperative function that can return a cleanup function@paramdeps If present, effect will only activate if the values in the list change.@version16.8.0@see{@link https://react.dev/reference/react/useEffect}
useEffect
, function useState<S>(initialState: S | (() => S)): [S, React.Dispatch<React.SetStateAction<S>>] (+1 overload)
Returns a stateful value, and a function to update it.
@version16.8.0@see{@link https://react.dev/reference/react/useState}
useState
} from "react";
type type Handler = (ev: Event) => voidHandler = (ev: Eventev: Event) => void; export default function
function useEvent(eventTarget?: EventTarget): {
    on: (eventName: string, eventHandler: Handler) => void;
    off: (eventName: string) => void;
}
useEvent
(eventTarget: EventTarget | undefinedeventTarget?: EventTarget) {
const [const eventListeners: Record<string, (ev: Event) => void>eventListeners, const setEventListeners: React.Dispatch<React.SetStateAction<Record<string, (ev: Event) => void>>>setEventListeners] = useState<Record<string, (ev: Event) => void>>(initialState: Record<string, (ev: Event) => void> | (() => Record<string, (ev: Event) => void>)): [Record<string, (ev: Event) => void>, React.Dispatch<...>] (+1 overload)
Returns a stateful value, and a function to update it.
@version16.8.0@see{@link https://react.dev/reference/react/useState}
useState
<type Record<K extends keyof any, T> = { [P in K]: T; }
Construct a type with a set of properties K of type T
Record
<string, ((ev: Eventev: Event) => void)>>({});
const const addEventListener: (eventName: string, eventHandler: Handler) => voidaddEventListener = useCallback<(eventName: string, eventHandler: Handler) => void>(callback: (eventName: string, eventHandler: Handler) => void, deps: React.DependencyList): (eventName: string, eventHandler: Handler) => void
`useCallback` will return a memoized version of the callback that only changes if one of the `inputs` has changed.
@version16.8.0@see{@link https://react.dev/reference/react/useCallback}
useCallback
((eventName: stringeventName:string, eventHandler: HandlereventHandler:type Handler = (ev: Event) => voidHandler)=>{
const setEventListeners: (value: React.SetStateAction<Record<string, (ev: Event) => void>>) => voidsetEventListeners(prev: Record<string, (ev: Event) => void>prev => { return { ...prev: Record<string, (ev: Event) => void>prev, [eventName: stringeventName]: eventHandler: HandlereventHandler } }); }, [eventTarget: EventTarget | undefinedeventTarget]); const const removeEventListener: (eventName: string) => voidremoveEventListener = useCallback<(eventName: string) => void>(callback: (eventName: string) => void, deps: React.DependencyList): (eventName: string) => void
`useCallback` will return a memoized version of the callback that only changes if one of the `inputs` has changed.
@version16.8.0@see{@link https://react.dev/reference/react/useCallback}
useCallback
((eventName: stringeventName:string)=>{
const setEventListeners: (value: React.SetStateAction<Record<string, (ev: Event) => void>>) => voidsetEventListeners(prev: Record<string, (ev: Event) => void>prev => { const
const draft: {
    [x: string]: (ev: Event) => void;
}
draft
= {...prev: Record<string, (ev: Event) => void>prev};
delete
const draft: {
    [x: string]: (ev: Event) => void;
}
draft
[eventName: stringeventName];
return
const draft: {
    [x: string]: (ev: Event) => void;
}
draft
;
}); }, [eventTarget: EventTarget | undefinedeventTarget]); function useEffect(effect: React.EffectCallback, deps?: React.DependencyList): void
Accepts a function that contains imperative, possibly effectful code.
@parameffect Imperative function that can return a cleanup function@paramdeps If present, effect will only activate if the values in the list change.@version16.8.0@see{@link https://react.dev/reference/react/useEffect}
useEffect
(() => {
const const target: EventTargettarget = eventTarget: EventTarget | undefinedeventTarget ?? var window: Window & typeof globalThis
[MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/window)
window
;
const
const listeners: {
    [x: string]: (ev: Event) => void;
}
listeners
= {...const eventListeners: Record<string, (ev: Event) => void>eventListeners};
// addListeners var Object: ObjectConstructor
Provides functionality common to all JavaScript objects.
Object
.
ObjectConstructor.entries<(ev: Event) => void>(o: {
    [s: string]: (ev: Event) => void;
} | ArrayLike<(ev: Event) => void>): [string, (ev: Event) => void][] (+1 overload)
Returns an array of key/values of the enumerable own properties of an object
@paramo Object that contains the properties and methods. This can be an object that you created or an existing Document Object Model (DOM) object.
entries
(
const listeners: {
    [x: string]: (ev: Event) => void;
}
listeners
).Array<[string, (ev: Event) => void]>.forEach(callbackfn: (value: [string, (ev: Event) => void], index: number, array: [string, (ev: Event) => void][]) => void, thisArg?: any): void
Performs the specified action for each element in an array.
@paramcallbackfn A function that accepts up to three arguments. forEach calls the callbackfn function one time for each element in the array.@paramthisArg An object to which the this keyword can refer in the callbackfn function. If thisArg is omitted, undefined is used as the this value.
forEach
(entry: [string, (ev: Event) => void]entry => {
eventTarget: EventTarget | undefinedeventTarget?.EventTarget.addEventListener(type: string, callback: EventListenerOrEventListenerObject | null, options?: AddEventListenerOptions | boolean): void
Appends an event listener for events whose type attribute value is type. The callback argument sets the callback that will be invoked when the event is dispatched. The options argument sets listener-specific options. For compatibility this can be a boolean, in which case the method behaves exactly as if the value was specified as options's capture. When set to true, options's capture prevents callback from being invoked when the event's eventPhase attribute value is BUBBLING_PHASE. When false (or not present), callback will not be invoked when event's eventPhase attribute value is CAPTURING_PHASE. Either way, callback will be invoked if event's eventPhase attribute value is AT_TARGET. When set to true, options's passive indicates that the callback will not cancel the event by invoking preventDefault(). This is used to enable performance optimizations described in § 2.8 Observing event listeners. When set to true, options's once indicates that the callback will only be invoked once after which the event listener will be removed. If an AbortSignal is passed for options's signal, then the event listener will be removed when signal is aborted. The event listener is appended to target's event listener list and is not appended if it has the same type, callback, and capture. [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventTarget/addEventListener)
addEventListener
(entry: [string, (ev: Event) => void]entry[0], entry: [string, (ev: Event) => void]entry[1]);
}); // remove old listeners return ()=> { var Object: ObjectConstructor
Provides functionality common to all JavaScript objects.
Object
.
ObjectConstructor.entries<(ev: Event) => void>(o: {
    [s: string]: (ev: Event) => void;
} | ArrayLike<(ev: Event) => void>): [string, (ev: Event) => void][] (+1 overload)
Returns an array of key/values of the enumerable own properties of an object
@paramo Object that contains the properties and methods. This can be an object that you created or an existing Document Object Model (DOM) object.
entries
(
const listeners: {
    [x: string]: (ev: Event) => void;
}
listeners
).Array<[string, (ev: Event) => void]>.forEach(callbackfn: (value: [string, (ev: Event) => void], index: number, array: [string, (ev: Event) => void][]) => void, thisArg?: any): void
Performs the specified action for each element in an array.
@paramcallbackfn A function that accepts up to three arguments. forEach calls the callbackfn function one time for each element in the array.@paramthisArg An object to which the this keyword can refer in the callbackfn function. If thisArg is omitted, undefined is used as the this value.
forEach
(entry: [string, (ev: Event) => void]entry => {
eventTarget: EventTarget | undefinedeventTarget?.EventTarget.removeEventListener(type: string, callback: EventListenerOrEventListenerObject | null, options?: EventListenerOptions | boolean): void
Removes the event listener in target's event listener list with the same type, callback, and options. [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventTarget/removeEventListener)
removeEventListener
(entry: [string, (ev: Event) => void]entry[0], entry: [string, (ev: Event) => void]entry[1]);
}); } }, [const eventListeners: Record<string, (ev: Event) => void>eventListeners]); return { on: (eventName: string, eventHandler: Handler) => voidon:const addEventListener: (eventName: string, eventHandler: Handler) => voidaddEventListener, off: (eventName: string) => voidoff:const removeEventListener: (eventName: string) => voidremoveEventListener } }

UseLockScroll

For accessibility-related work, I ended up needing a way to disable scroll lock when a modal component is mounted.

import {function useLayoutEffect(effect: React.EffectCallback, deps?: React.DependencyList): void
The signature is identical to `useEffect`, but it fires synchronously after all DOM mutations. Use this to read layout from the DOM and synchronously re-render. Updates scheduled inside `useLayoutEffect` will be flushed synchronously, before the browser has a chance to paint. Prefer the standard `useEffect` when possible to avoid blocking visual updates. If you’re migrating code from a class component, `useLayoutEffect` fires in the same phase as `componentDidMount` and `componentDidUpdate`.
@version16.8.0@see{@link https://react.dev/reference/react/useLayoutEffect}
useLayoutEffect
} from "react";
export default function function useLockScroll(): voiduseLockScroll() { function useLayoutEffect(effect: React.EffectCallback, deps?: React.DependencyList): void
The signature is identical to `useEffect`, but it fires synchronously after all DOM mutations. Use this to read layout from the DOM and synchronously re-render. Updates scheduled inside `useLayoutEffect` will be flushed synchronously, before the browser has a chance to paint. Prefer the standard `useEffect` when possible to avoid blocking visual updates. If you’re migrating code from a class component, `useLayoutEffect` fires in the same phase as `componentDidMount` and `componentDidUpdate`.
@version16.8.0@see{@link https://react.dev/reference/react/useLayoutEffect}
useLayoutEffect
(() => {
const const previous: stringprevious = var window: Window & typeof globalThis
[MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/window)
window
.function getComputedStyle(elt: Element, pseudoElt?: string | null): CSSStyleDeclaration
[MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/getComputedStyle)
getComputedStyle
(var document: Document
[MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/document)
document
.Document.body: HTMLElement
Specifies the beginning and end of the document body. [MDN Reference](https://developer.mozilla.org/docs/Web/API/Document/body)
body
).CSSStyleDeclaration.overflow: string
[MDN Reference](https://developer.mozilla.org/docs/Web/CSS/overflow)
overflow
;
var document: Document
[MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/document)
document
.Document.body: HTMLElement
Specifies the beginning and end of the document body. [MDN Reference](https://developer.mozilla.org/docs/Web/API/Document/body)
body
.ElementCSSInlineStyle.style: CSSStyleDeclaration
[MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLElement/style)
style
.CSSStyleDeclaration.overflow: string
[MDN Reference](https://developer.mozilla.org/docs/Web/CSS/overflow)
overflow
= "hidden";
// undo on unmount return () => { var document: Document
[MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/document)
document
.Document.body: HTMLElement
Specifies the beginning and end of the document body. [MDN Reference](https://developer.mozilla.org/docs/Web/API/Document/body)
body
.ElementCSSInlineStyle.style: CSSStyleDeclaration
[MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLElement/style)
style
.CSSStyleDeclaration.overflow: string
[MDN Reference](https://developer.mozilla.org/docs/Web/CSS/overflow)
overflow
= const previous: stringprevious
}; }, []); }

Note

For now these have been my most used hooks. As my React journey continues, I may update this post to include new hooks and explain their uses.

✌🏽