useReact: My go-to React Hooks
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.useReducer} from "react";
export default function function useRender(): {
render: React.ActionDispatch<[]>;
}
useRender() {
const [const count: number
count, 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.useReducer(n: number
n => n: number
n + 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.useCallback, function useEffect(effect: React.EffectCallback, deps?: React.DependencyList): void
Accepts a function that contains imperative, possibly effectful code.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.useState} from "react";
type type Handler = (ev: Event) => void
Handler = (ev: Event
ev: Event) => void;
export default function function useEvent(eventTarget?: EventTarget): {
on: (eventName: string, eventHandler: Handler) => void;
off: (eventName: string) => void;
}
useEvent(eventTarget: EventTarget | undefined
eventTarget?: 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.useState<type Record<K extends keyof any, T> = { [P in K]: T; }
Construct a type with a set of properties K of type TRecord<string, ((ev: Event
ev: Event) => void)>>({});
const const addEventListener: (eventName: string, eventHandler: Handler) => void
addEventListener = 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.useCallback((eventName: string
eventName:string, eventHandler: Handler
eventHandler:type Handler = (ev: Event) => void
Handler)=>{
const setEventListeners: (value: React.SetStateAction<Record<string, (ev: Event) => void>>) => void
setEventListeners(prev: Record<string, (ev: Event) => void>
prev => {
return {
...prev: Record<string, (ev: Event) => void>
prev,
[eventName: string
eventName]: eventHandler: Handler
eventHandler
}
});
}, [eventTarget: EventTarget | undefined
eventTarget]);
const const removeEventListener: (eventName: string) => void
removeEventListener = 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.useCallback((eventName: string
eventName:string)=>{
const setEventListeners: (value: React.SetStateAction<Record<string, (ev: Event) => void>>) => void
setEventListeners(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: string
eventName];
return const draft: {
[x: string]: (ev: Event) => void;
}
draft;
});
}, [eventTarget: EventTarget | undefined
eventTarget]);
function useEffect(effect: React.EffectCallback, deps?: React.DependencyList): void
Accepts a function that contains imperative, possibly effectful code.useEffect(() => {
const const target: EventTarget
target = eventTarget: EventTarget | undefined
eventTarget ?? 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 objectentries(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.forEach(entry: [string, (ev: Event) => void]
entry => {
eventTarget: EventTarget | undefined
eventTarget?.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 objectentries(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.forEach(entry: [string, (ev: Event) => void]
entry => {
eventTarget: EventTarget | undefined
eventTarget?.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) => void
on:const addEventListener: (eventName: string, eventHandler: Handler) => void
addEventListener,
off: (eventName: string) => void
off:const removeEventListener: (eventName: string) => void
removeEventListener
}
}
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`.useLayoutEffect} from "react";
export default function function useLockScroll(): void
useLockScroll() {
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`.useLayoutEffect(() => {
const const previous: string
previous = 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: string
previous
};
}, []);
}
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.
✌🏽