Last active
November 19, 2019 00:02
-
-
Save tokdaniel/39e3d2a4a17581ed4e1e771e111532f8 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { useState, useEffect, useRef, EffectCallback } from 'react'; | |
import throttle from 'lodash/throttle'; | |
import debounce from 'lodash/debounce'; | |
// Ties two states together, and tracks only if a specific case occur. | |
export const transitionTracker = <A, B>(from: A, to: B) => { | |
let marker = 0; | |
return (state1: A, state2: B) => { | |
if (from === state1 && to === state2) { | |
marker = marker + 1; | |
} | |
return marker; | |
}; | |
}; | |
type ScrollY = number; | |
type UseScrollYHook = ( | |
updateOptions?: { | |
limit?: number; | |
method?: 'THROTTLE' | 'DEBOUNCE'; | |
leading?: boolean; | |
trailing?: boolean; | |
}) => ScrollY; | |
//Provides the actual scrollY number with options to throttle, or debounce the value updates. | |
export const useScrollY: UseScrollYHook = (options) => { | |
const [scrollY, registerScrollY] = useState(window.scrollY); | |
const { limit, method, leading, trailing } = options; | |
let update = () => { | |
registerScrollY(window.scrollY); | |
}; | |
if (limit) { | |
if (method === 'THROTTLE') { | |
update = throttle(update, limit, { leading, trailing }); | |
} else if (method === 'DEBOUNCE') { | |
update = debounce(update, limit, { leading, trailing }); | |
} | |
} | |
useEffect(() => { | |
window.addEventListener('scroll', update); | |
return function cleanup() { | |
window.removeEventListener('scroll', update); | |
}; | |
}, []); | |
return scrollY; | |
}; | |
// Save the state of something in a previous render cycle. | |
export const usePrevious = (value) => { | |
const ref = useRef(); | |
useEffect(() => { | |
ref.current = value; | |
}, [value]); | |
return ref.current; | |
}; | |
// track the transition when wasScrolledToBottom is FALSE and isScrolledToBottom is TRUE only. | |
const track = transitionTracker<boolean, boolean>(false, true); | |
// Triggers an effect, when the page was NOT SCROLLED to BOTTOM at the previous render and it | |
// IS SCROLLED to BOTTOM currently. | |
export const triggerEffectAtBottom = ( | |
effect: EffectCallback, | |
ignoreFirst = true, | |
offsetBottom = 0, | |
) => { | |
const scrollY = useScrollY({ | |
limit: 50, | |
method: 'THROTTLE', | |
leading: true, | |
}); | |
const scrollableDist = document.body.scrollHeight - window.innerHeight; | |
const isScrolledToBottom = scrollY - offsetBottom === scrollableDist; | |
const wasScrolledToBottom = usePrevious(isScrolledToBottom); | |
const isFirstRun = useRef(true); | |
useEffect(() => { | |
if (isFirstRun.current && ignoreFirst) { | |
isFirstRun.current = false; | |
return; | |
} else if (isScrolledToBottom && !wasScrolledToBottom) { | |
effect(); | |
} | |
}, [track(wasScrolledToBottom, isScrolledToBottom)]); | |
}; | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment