Skip to content

Instantly share code, notes, and snippets.

@cahilfoley
Last active June 14, 2019 13:32
Show Gist options
  • Save cahilfoley/10e27040bd885b588805a0b2a943de1d to your computer and use it in GitHub Desktop.
Save cahilfoley/10e27040bd885b588805a0b2a943de1d to your computer and use it in GitHub Desktop.
[React Hooks] Random React hooks that I've come across and thought were usefull #react #hooks
import { useCallback, useLayoutEffect, useState, useRef } from 'react'
/**
* Returns the bounding client rect of a HTML element, uses the `ResizeObserver` api if available to detect changes to the
* size. Falls back to listening for resize events on the window.
*/
export function useBoundingRect<T extends HTMLElement>(): [React.Ref<T>, ClientRect | DOMRect] {
const ref = useRef<T>()
const [rect, setRect] = useState()
const recalculator = useCallback(() => {
if (ref.current) {
setRect(ref.current.getBoundingClientRect())
}
}, [ref])
// Handle changing of size
useLayoutEffect(() => {
const { ResizeObserver } = window
if (!ref.current) return
recalculator()
window.addEventListener('resize', recalculator)
let resizeObserver: ResizeObserver
if (typeof ResizeObserver === 'function') {
resizeObserver = new ResizeObserver(recalculator)
resizeObserver.observe(ref.current)
}
return () => {
window.removeEventListener('resize', recalculator)
if (resizeObserver) resizeObserver.disconnect()
}
}, [ref, recalculator])
// Handle document scroll
useLayoutEffect(() => {
document.addEventListener('scroll', recalculator)
return () => document.removeEventListener('scroll', recalculator)
})
return [ref, rect]
}
import { useCallback, useLayoutEffect, useState, useRef } from 'react'
// For the implmentation of this see https://gist.github.com/849805a6e0c4e3d71619f278e495f105#file-measurement-ts
import { getSize } from '../utils'
/**
* Returns the height and width of a HTML element, uses the `ResizeObserver` api if available to detect changes to the
* size. Falls back to listening for resize events on the window.
*/
export function useComponentSize<T extends HTMLElement>(): [
React.Ref<T>,
ReturnType<typeof getSize>
] {
const ref = useRef<T>()
const [size, setSize] = useState(getSize(ref.current))
const resizeHandler = useCallback(() => {
if (ref.current) {
setSize(getSize(ref.current))
}
}, [ref])
useLayoutEffect(() => {
const { ResizeObserver } = window
if (!ref.current) return
resizeHandler()
if (typeof ResizeObserver === 'function') {
const resizeObserver = new ResizeObserver(resizeHandler)
resizeObserver.observe(ref.current)
return () => resizeObserver.disconnect()
} else {
window.addEventListener('resize', resizeHandler)
return () => window.removeEventListener('resize', resizeHandler)
}
}, [ref, resizeHandler])
return [ref, size]
}
import { useEffect, useRef } from 'react'
type EventHandler<T extends Event> = (event: T) => any
export function useEventListener<T extends keyof DocumentEventMap>(
eventName: T,
handler: EventHandler<DocumentEventMap[T]>,
element: HTMLElement = document.documentElement,
) {
const savedHandler = useRef<EventHandler<DocumentEventMap[T]>>()
// Update a ref to the latest handler whenever it changes
useEffect(() => {
savedHandler.current = handler
}, [handler])
useEffect(() => {
// Ensure add event listener is supported
if (!(element && element.addEventListener)) return
// Use the latest event handler in the listener
const eventListener = (event: DocumentEventMap[T]) =>
savedHandler.current && savedHandler.current(event)
// Add listener and cleanup
element.addEventListener(eventName, eventListener)
return () => element.removeEventListener(eventName, eventListener)
}, [eventName, element])
}
import { useRef, useState } from 'react'
import { useEventListener } from './useEventListener'
interface UseMousePositionOptions {
/** Throttle the updates to only occur every x ms */
throttle?: number
}
export function useMousePosition(options: UseMousePositionOptions = {}) {
const [position, setPosition] = useState(() => ({ clientX: 0, clientY: 0 }))
const lastUpdate = useRef(0)
useEventListener('mousemove', ({ clientX, clientY }) => {
const now = Date.now()
if (options.throttle && now - lastUpdate.current < options.throttle) return
setPosition({ clientX, clientY })
lastUpdate.current = now
})
return [position]
}
import { useRef, useState, useEffect } from 'react'
// For the implementation of this check out this gist https://gist.github.com/cahilfoley/849805a6e0c4e3d71619f278e495f105#file-sharedeventlistener-ts
import { SharedEventListener } from '../utils'
interface UseMousePositionOptions {
/** Throttle the updates to only occur every x ms */
throttle?: number
}
const mouseMoveListener = new SharedEventListener('mousemove')
/**
* This is a highly optimised version of the standard `useMousePosition` hook that shares a single event listener on the document which detaches itself
* when the last component unmounts.
*/
export function useSharedMousePosition(options: UseMousePositionOptions = {}) {
const [position, setPosition] = useState(() => ({ clientX: 0, clientY: 0 }))
const lastUpdate = useRef(0)
useEffect(() => {
const unsubscribe = mouseMoveListener.subscribe(({ clientX, clientY }: MouseEvent) => {
const now = Date.now()
if (options.throttle && now - lastUpdate.current < options.throttle) return
setPosition({ clientX, clientY })
lastUpdate.current = now
})
return unsubscribe
}, [options.throttle])
return [position]
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment