Assorted React hooks.
$ npm install -S @haensl/react-hooks
$ yarn add @haensl/react-hooks
-
Use hooks in your components:
import { useDebounce } from '@haensl/react-hooks';
const DebouncedButton = () => {
const handler = useDebounce(() => {
console.log('click');
}, 50);
return (
<button
onClick={ handler }
>click me</button>
);
};
useAnimationFrame
: animate a function.useBoundingClientRect
: keep track of a container's DOM rectangle.useClassNames
: compile CSS class names from state.useDebounce
: debounce a function.useInterval
: use an interval.useIsMounted
: keep track of whether or not a component is mounted.useIsomorphicLayoutEffect
: use this instead ofuseLayoutEffect
if your app uses serverside rendering (SSR).useIsScrolling
: keep track of whether or not the user is scrolling.useLang
: use the browser's language setting.useOnScroll
: subscribe to scroll events.usePrevious
: keep track of a variable's previous value.useTimeout
: use a timeout.useWindowScroll
: keep track of thewindow
's scroll position.useWindowSize
: keep track of thewindow
's size.
Uses requestAnimationFrame
to animate a function fn
. The callback is passed one single argument, the time delta in milliseconds that has passed between this and the last call. Please check the example below as well as the Codepen example.
import React, { useState, useEffect } from 'react';
import { useAnimationFrame } from '@haensl/react-hooks';
const AnimatedTimer = () => {
const [seconds, setSeconds] = useState(0);
const [elapsed, setElapsed] = useState(0);
useAnimationFrame((dt) => {
setElapsed(elapsed + dt);
});
useEffect(() => {
if (elapsed >= 1000) {
setSeconds(seconds + 1);
setElapsed(elapsed - 1000);
}
}, [elapsed]);
return (
<span>{ seconds }</span>
);
};
Returns the DOM rectangle (initially null
) as returned by getBoundingClientRect
for the given container ref
. Changes are debounced by 25 milliseconds by default. Customize the debounce interval via the optional debounceMs
argument. Please check out the example below as well as the Codepen example.
import React, { useRef } from 'react';
import { useBoundingClientRect } from '@haensl/react-hooks';
const RectTracker = () => {
const ref = useRef();
const containerRect = useBoundingClientRect(ref);
if (!containerRect) {
return (
<div ref={ ref }>
<span>no container rect</span>
</div>
);
}
return (
<div ref={ ref }>
<span>Container rect:</span>
<span>Width: {containerRect.width}</span>
<span>Height: {containerRect.height}</span>
</div>
);
};
Compiles a states
object into a CSS class name string. By default all keys in states
are joined by a space (' '
) but you can supply a custom separator
to cater to the needs of your CSS module naming methodology of choice. Please check the examples below.
import React, { useState } from 'react';
import { useClassNames } from '@haensl/react-hooks';
const MyComponent = () => {
const [stateA, setStateA] = useState(false);
const className = useClassNames({
MyComponent: true, // always have MyComponent in class name
MyComponent--stateA: stateA // add MyComponent--stateA when stateA is true
});
// className will be 'MyComponent' or 'MyComponent MyComponent--stateA'
return (
<div className={ className }>
{
// render content
}
</div>
);
};
import React, { useState } from 'react';
import { useClassNames } from '@haensl/react-hooks';
const MyComponent = () => {
const [stateA, setStateA] = useState(false);
const className = useClassNames(
{
MyComponent: true, // always have MyComponent in class name
stateA // add --stateA when stateA is true
},
'--'
);
// className will either be 'MyComponent' or 'MyComponent--stateA'
return (
<div className={ className }>
{
// render content
}
</div>
);
};
Uses memoization to debounce fn
by debounceMs
milliseconds. Please check the example below as well as the Codepen example.
import React from 'react';
import { useDebounce } from '@haensl/react-hooks';
const DebouncedButton = () => {
const handler = useDebounce(() => {
console.log('click');
}, 50); // handler only fires when there were no calls for 50ms.
return (
<button
onClick={ handler }
>click me</button>
);
};
Calls a fn
repeatedly every intervalMs
milliseconds.
import React, { useState, useCallback } from 'react';
import { useInterval } from '@haensl/react-hooks';
const MyAnimation = () => {
const [frame, setFrame] = useState(0);
// Update frame every 100 milliseconds
useInterval(() => {
setFrame((frame) => frame + 1);
}, 100);
return (
<div>{ frame }</div>
);
};
Returns a function
to check whether or not the component invoking the hook is mounted.
import React, { useEffect } from 'react';
import { useIsMounted } from '@haensl/react-hooks';
import api from 'somewhere';
const MyComponent = () => {
const isMounted = useIsMounted();
// load some data from the backend
useEffect(() => {
api.fetchData()
.then((data) => {
if (isMounted()) {
// use data only if component is still mounted
}
});
}, []);
}
This hooks resolves the common React warning when using useLayoutEffect
in a serverside environment:
Warning: useLayoutEffect does nothing on the server, because its effect cannot be encoded into the server renderer’s output format. This will lead to a mismatch between the initial, non-hydrated UI and the intended UI. To avoid this, useLayoutEffect should only be used in components that render exclusively on the client. See https://fb.me/react-uselayouteffect-ssr for common fixes.
useIsomorphicLayoutEffect
resolves to useLayoutEffect
on the client and useEffect
on the server. Use this hook instead of useLayoutEffect
if your app uses serverside rendering (SSR).
import React, { useRef } from 'react';
import { useIsomorphicLayoutEffect } from '@haensl/react-hooks';
const MyComponent = () => {
const ref = useRef();
// prevents serverside rendering warning
useIsomorphicLayoutEffect(() => {
if (ref.current) {
// do stuff with ref
}
}, [ref]);
return (
<div ref={ ref }>
// ...
</div>
)
}
Returns a boolean
indicating whether or not the user is scrolling. You can subscribe to a specific element via the first argument, el
(default: window
). End of scrolling is determined by no incoming scroll events for scrollEndMs
milliseconds (default: 100
). Please check the example blow as well as the Codepen example
import React from 'react';
import { useIsScrolling } from '@haensl/react-hooks';
const UserScrollTracker = () => {
const isScrolling = useIsScrolling();
return (
<span>The user is currently { isScrolling ? '' : 'not' } scrolling</span>
);
};
Returns the user's language setting from navigator.language
. Use the defaultLang
of the options parameter to set a default language. (default: 'en
).
import React from 'react';
import { useLang } from '@haensl/react-hooks';
const MyComponent = () => {
const lang = useLang();
return (
<span>The user's preferred language is { lang }.</span>
);
};
Subscribes to scroll
events on the given element el
(default: window
). The callback function fn
is passed the Event
. Please check the example below as well as the Codepen example.
import React, { useState } from 'react';
import { useOnScroll } from '@haensl/react-hooks';
const WindowScrollTracker = () => {
const [windowScroll, setWindowScroll] = useState(0);
useOnScroll(() => {
setWindowScroll(window.scrollY);
});
return (
<div className="WindowScrollTracker">
<span>Window has scrolled down</span>
<span>{ windowScroll }px</span>
</div>
);
};
Keeps track of changes to a value, storing it's previous state.
import { useEffect, useState } from 'react';
import { usePrevious, useWindowScroll } from '@haensl/react-hooks';
const ScrollDirectionTracker = () => {
const scrollPosition = useWindowScroll();
const previousScrollPosition = usePrevious(scrollPosition);
const [scrollDirection, setScrollDirection] = useState('down');
useEffect(() => {
if (previousScrollPosition.y < scrollPosition.y) {
setScrollDirection('down');
} else if (previousScrollPosition.y > scrollPosition.y) {
setScrollDirection('up');
}
}, [scrollPosition, previousScrollPosition]);
return (
<div className="ScrollDirectionTracker">
<span>User is scrolling</span>
<span>{ scrollDirection }px</span>
</div>
);
};
Calls fn
once after intervalMs
milliseconds.
import React, { useState, useCallback } from 'react';
import { useClassNames, useTimeout } from '@haensl/react-hooks';
const MyComponent = () => {
const [animate, setAnimate] = useState(false);
// Start animation after 1s
useTimeout(() => {
setAnimate(true);
}, 1000);
const className = useClassNames({
animate
});
return (
<div
className={ className }
>
// ...
</div>
);
};
Returns an object (null
if there is no window
) with properties x
and y
reflecting the the scroll position of the window
or document
. Scroll position updates are by default debounced by 25 milliseconds. This debounce interval can be customized via the optional debounceMs
argument. Please check the example below as well as the Codepen example.
import React, { useState } from 'react';
import { useWindowScroll } from '@haensl/react-hooks';
const windowScrollTracker = () => {
const windowScroll = useWindowScroll();
if (!windowScroll) {
return (
<div className="WindowScrollTracker">
no scroll poistion
</div>
);
}
return (
<div className="WindowScrollTracker">
<span>Scroll x: {windowScroll.x}</span>
<span>Scroll y: {windowScroll.y}</span>
</div>
);
};
Returns an object (initially null
) with properties width
and height
reflecting the innerWidth
and innerHeight
of the window
object. Size updates are by default debounced by 25 milliseconds. This debounce interval can be customized via the optional debounceMs
argument. Please check the example below as well as the Codepen example.
import React, { useState } from 'react';
import { useWindowSize } from '@haensl/react-hooks';
const WindowSizeTracker = () => {
const windowSize = useWindowSize();
if (!windowSize) {
return (
<div className="WindowSizeTracker">
<span>No window size</span>
</div>
);
}
return (
<div className="WindowSizeTracker">
<span>Window Size:</span>
<span>width: { windowSize.width }px</span>
<span>height: { windowSize.height }px</span>
</div>
);
};