Generic React hooks for daily development.
useAsyncEffect: like useEffect
but you can pass an async function and check if the component restarted the effect during your async operation.
useAsyncEffect(
async ({isStale, setCancel}) => {
const fetchPromise = fetch(`http://example.com/users/${userId}`);
// using setCancel, unfinished effect runs are cancelled by new runs
// this allows you to cancel requests you no longer need
setCancel(() => {
console.log("dependencies (userId) changed, cancelling old request!")
// your async API must have some kind of cancellation
// for example, the fetch API can use an AbortController
fetchPromise.cancel();
})
const data = await fetchPromise;
if (!isStale())
setState(data);
},
[userId]
).catch(err => {
console.log("an error occurred!");
console.error(err);
});
const [activeType, setActiveType] = useState<string>();
useOnChange(({prev}) => {
const [prevType] = prev;
uiToastNotifyUser(`active type changed from ${prevType} to ${activeType}`);
}, [activeType]);
or wait for some third party non-react state, the effect reruns every render if the validity condition was not met, it only stops running once it's met and none of the listened states have changed.
const viewport = thirdpartyLibrary.tryGetViewport();
const [userDefinedColor] = useState("red");
useOnChange(() => {
// we know viewport is defined because of the condition
viewport!.setBackgroundColor(userDefinedColor);
}, viewport !== undefined, [userDefinedColor]);
n
millisecondsuseInterval
but with the same API for async effects as useAsyncEffect
prev=>next
callbacks)
e.g.:
const [myobj, setMyobj] = useState({time: 5, area: "world"});
const setArea = usePropertySetter("area", setMyobj);
useEffect(() => {
setArea(prevArea => prevArea === "world" ? "hello" : "world");
}, []);
return <MySubcomponent setArea={setArea} />;
const [value, input, setInput] = useValidatedInput("5", {
// don't change the numeric parsing but validate that it's a positive number
validate: (n: number) => {
const valid = /\d+/.test(n);
return { valid, status: !valid ? "only positive integers" : undefined };
})
});
return <input value={input} onChange={e => setInput(e.currentTarget.value)} />;
const [isOpen, setIsOpen] = useState(true);
const popupElemRef = useRef<HTMLDivElement>(null);
useOnExternalClick(popupElemRef, () => setIsOpen(false)) // close popup if user clicks outside of it
return (
<div>
<Toolbar />
{isOpen && <div ref={popupElemRef}><UserPopup /></div>}
</div>
);
To get eslint-plugin-react-hooks
to warn on bad dependencies for hooks like
useAsyncEffect
, see the eslint rule's advanced configuration docs.
Older versions of eslint-plugin-react-hooks
may warn on passing an async argument, we have a PR in react's monorepo to fix that eslint rule, and we can maintain a trivial fork if this is a common issue, because not warning on missed effects almost always leads to bug.