I hereby claim:
- I am jbmanning on github.
- I am jbmanning (https://keybase.io/jbmanning) on keybase.
- I have a public key whose fingerprint is 449E AF73 6B12 A54E 10EB 9911 7AD4 95EC 4A13 CFE4
To claim this, I am signing this object:
| import { useEffect, useRef, useState } from "react"; | |
| import debounce from "lodash.debounce"; | |
| import useResizeObserver from "useResizeObserver"; | |
| const SINGLE_CHILD_WARNING = "Warning: Expected a single child node, aborting..."; | |
| const NO_NODE_WARNING = "Warning: useMaxPageScroll received an invalid node. Ignoring."; | |
| /** | |
| * Hook to prevent scrolling jitters. Watches the provided DOM node and | |
| * maintains a min-height of the distance to the bottom of the screen, relative to | 
| import { useEffect } from "react"; | |
| let nodeList = new Map(); | |
| let observer = new window.ResizeObserver((entries) => { | |
| for (const entry of entries) { | |
| if (nodeList.has(entry.target)) { | |
| nodeList.get(entry.target)(entry); | |
| } | |
| } | |
| }); | 
| export function arrayToObj(arr, mapper = (obj) => [obj.id, obj]) { | |
| const obj = {}; | |
| for (let i = 0; i < arr.length; i++) { | |
| const [k, v] = mapper(arr[i], i); | |
| obj[k] = v; | |
| } | |
| return obj; | |
| } | 
| export function useAnalytics(session) { | |
| const logAnalytic = useCallback( | |
| (props) => api.post("/log-analytic", { session, ...props }).catch(() => {}), | |
| [session] | |
| ); | |
| // Track all clicks for links on the page. This will also track any internal page clicks. | |
| // If you do not want to track internal page clicks, this needs to change to something like: | |
| // if (a && a.href is not same domain) logAnalytic({ ... }); | |
| useEffect(() => { | 
| function useMediaQuery(queryString) { | |
| const [systemMatches, setSystemMatches] = useState(() => window.matchMedia(queryString).matches); | |
| const listener = useCallback((mq) => setSystemMatches(mq.matches), [setSystemMatches]); | |
| useEffect(() => { | |
| const mq = window.matchMedia(queryString); | |
| mq.addEventListener('change', listener); | |
| return () => mq.removeEventListener('change', listener); | |
| }, [listener, queryString]); | 
| const LocalStorageKeys = { | |
| ThemeSetting: "ThemeSetting", | |
| }; | |
| const ThemeSettings = { | |
| Auto: null, | |
| Light: false, | |
| Dark: true, | |
| }; | 
| const DefaultBackoffConfig = { | |
| initialDelay: 750, | |
| multiplier: 3, | |
| maxAttempts: 4, | |
| jitter: .15, | |
| retryTimeout: 30 * 1000, | |
| onCall: () => {}, | |
| onError: () => {}, | |
| onComplete: () => {}, | |
| }; | 
| export function rateLimit(func, millis, immediate = true) { | |
| let pending, lastCall, argsToCallWith, context; | |
| if (!millis || millis < 0) millis = 100; | |
| function callFn(now) { | |
| pending = undefined; | |
| lastCall = now ? now : Date.now(); | |
| func.apply(context, argsToCallWith); | |
| } | 
I hereby claim:
To claim this, I am signing this object: