usehooks-ts
usehooks-ts copied to clipboard
useElementSize ref not passable to other hooks
It is currently not possible to easily pass the ref exposed from useElementSize
to other hooks.
As the useElementSize
hook internally stores the ref in a state while exposing the ref via the type (node: T | null) => void
it is not a "normal" RefObject thus not useable by passing it to other hooks.
My usecase is having 1 ref that is attached to a scrollable container. I'd like to monotor this elements size as well as attaching the scroll
event handler.
const [scrollableRef, { height: scrollableElementHeight }] = useElementSize<HTMLUListElement>();
const handleScroll = () => { ... }
useEventListener('scroll', handleScrollList, scrollableRef);
This causes the scroll
event handler to never fire while triggering a Typescript error.
No overload matches this call.
It'd be nice if the useElementSize
exposes a value that can be passed around to other hooks and places as it were a normal RefObject
.
I've hacked up a workaround by exposing the ref as a normal RefObject<T> being a third element in the return array.
Resulting in the type:
function useElementSize<T extends HTMLElement = HTMLDivElement>(): [(node: T | null) => void, Size, RefObject<T>]
This is achieved by doing the following:
import { RefObject, useCallback, useState } from 'react';
// See: https://usehooks-ts.com/react-hook/use-event-listener
import useEventListener from './useEventListener';
// See: https://usehooks-ts.com/react-hook/use-isomorphic-layout-effect
import useIsomorphicLayoutEffect from './useIsomorphicLayoutEffect';
interface Size {
width: number;
height: number;
}
function useElementSize<T extends HTMLElement = HTMLDivElement>(): [
(node: T | null) => void,
Size,
RefObject<T>
] {
// Mutable values like 'ref.current' aren't valid dependencies
// because mutating them doesn't re-render the component.
// Instead, we use a state as a ref to be reactive.
const [ref, setRef] = useState<T | null>(null);
const [size, setSize] = useState<Size>({
width: 0,
height: 0
});
// Prevent too many rendering using useCallback
const handleSize = useCallback(() => {
setSize({
width: ref?.offsetWidth || 0,
height: ref?.offsetHeight || 0
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [ref?.offsetHeight, ref?.offsetWidth]);
useEventListener('resize', handleSize);
useIsomorphicLayoutEffect(() => {
handleSize();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [ref?.offsetHeight, ref?.offsetWidth]);
return [setRef, size, { current: ref }];
}
export default useElementSize;
In the component this usage looks like this:
const [scrollableRef, { width, height }, scrollableRefInstance] = useElementSize<HTMLUListElement>();
...
<Component ref={scrollableRef}/>
Although the workaround does work, I am personally not a huge fan of the double ref in the returned array.
Maybe someone has a better idea of implementing the useElementSize
in such a way that 1 single ref is exposed while maintaining limited rerenders when resizing and so forth. - while having it be of type RefObject obviously.
Hi, useElementSize
has been deprecated and should be replaced by useResizeObserver
.