Skip to content

Instantly share code, notes, and snippets.

@GoodNovember
Last active November 18, 2019 18:16
Show Gist options
  • Save GoodNovember/e7bfebaefc76c9005306585ce4ea7606 to your computer and use it in GitHub Desktop.
Save GoodNovember/e7bfebaefc76c9005306585ce4ea7606 to your computer and use it in GitHub Desktop.
Some Handy React Hooks
const Range = ({ containerStyle, containerClassName, min, max, value, step, onChange }) => {
const animate = (timestamp, delta) => {
console.log('AnimationTick', {timestamp, delta})
}
const [startAnimation, stopAnimation, getIsAnimatingRef, getIsAnimatingState] = useAnimationFrame(animate, true)
useDocumentEvents({
'pointerup' (event) {
stopAnimation()
},
'pointermove' (event) {
if (getIsAnimatingRef()) {
console.log('[Document Pointer Move]', event)
}
}
})
const pointerDownEventHandlerForRangeDot = event => {
if (getIsAnimatingRef() !== true) {
startAnimation()
}
}
return (
<div>
<div>
{ getIsAnimatingState() ? 'Animating' : 'Stopped' }
</div>
<div style={containerStyle} className={`range ${containerClassName}`}>
<div className='range-dot' style={{ background: 'red' }}
onPointerDown={pointerDownEventHandlerForRangeDot}
onPointerUp={() => stopAnimation()}
onPointerCancel={() => stopAnimation()}
/>
</div>
</div>
)
}
/* globals requestAnimationFrame, cancelAnimationFrame */
import { useRef, useEffect, useState } from 'react'
export const useAnimationFrame = (onFrameTickCallback, startPaused = false) => {
const requestAnimationFrameReference = useRef()
const previousTimeReference = useRef()
const animationStateReference = useRef()
const [ isAnimating, setIsAnimating ] = useState(false)
const animate = timestamp => {
if (previousTimeReference.current !== undefined) {
const deltaTime = timestamp - previousTimeReference.current
animationStateReference.current = true
setIsAnimating(true)
onFrameTickCallback(timestamp, deltaTime)
}
previousTimeReference.current = timestamp
requestAnimationFrameReference.current = requestAnimationFrame(animate)
}
const stopAnimation = () => {
animationStateReference.current = false
setIsAnimating(false)
previousTimeReference.current = undefined
cancelAnimationFrame(requestAnimationFrameReference.current)
}
const startAnimation = () => {
requestAnimationFrameReference.current = requestAnimationFrame(animate)
}
const getIsAnimatingState = () => {
return isAnimating
}
const getIsAnimatingRef = () => {
return !!animationStateReference.current
}
useEffect(() => {
if (startPaused === false) {
startAnimation()
}
return () => stopAnimation()
}, [])
return [startAnimation, stopAnimation, getIsAnimatingRef, getIsAnimatingState]
}
import { useEffect } from 'react'
export const useDocumentEvents = (eventMapObject) => {
const rawEvents = Object.keys(eventMapObject).map((eventName) => {
const callback = eventMapObject[eventName]
return {
eventName,
callback
}
})
useEffect(() => {
rawEvents.map(({ eventName, callback }) => {
document.addEventListener(eventName, callback)
})
return () => {
rawEvents.map(({ eventName, callback }) => {
document.removeEventListener(eventName, callback)
})
}
}, [])
}
import { useEffect } from 'react'
export const useElementEvents = (element, eventMapObject) => {
const rawEvents = Object.keys(eventMapObject).map((eventName) => {
const callback = eventMapObject[eventName]
return {
eventName,
callback
}
})
useEffect(() => {
rawEvents.map(({ eventName, callback }) => {
element.addEventListener(eventName, callback)
})
return () => {
rawEvents.map(({ eventName, callback }) => {
element.removeEventListener(eventName, callback)
})
}
}, [])
}
import { useEffect } from 'react'
export const useGamepads = () => {
const connectSubscribers = new Set()
const disconnectSubscribers = new Set()
const handleGamepadConnected = event => {
for (const subscriber of connectSubscribers.values()) {
subscriber(event)
}
}
const handleGamepadDisconnected = event => {
for (const subscriber of disconnectSubscribers.values()) {
subscriber(event)
}
}
useEffect(() => {
window.addEventListener('gamepadconnected', handleGamepadConnected)
window.addEventListener('gamepaddisconnected', handleGamepadDisconnected)
return () => {
connectSubscribers.clear()
disconnectSubscribers.clear()
window.removeEventListener('gamepadconnected', handleGamepadConnected)
window.removeEventListener('gamepaddisconnected', handleGamepadDisconnected)
}
}, [])
return {
onGamepadConnected (callback) {
if (connectSubscribers.has(callback) === false) {
connectSubscribers.add(callback)
}
return () => {
if (connectSubscribers.has(callback)) {
connectSubscribers.delete(callback)
}
}
},
onGamepadDisconnected (callback) {
if (disconnectSubscribers.has(callback) === false) {
disconnectSubscribers.add(callback)
}
return () => {
if (disconnectSubscribers.has(callback)) {
disconnectSubscribers.delete(callback)
}
}
},
getGamepads () {
return navigator.getGamepads()
},
makeButtonLogicTestMaker (gamepadInstance) {
return ({ testFn, callback }) => {
if (testFn(gamepadInstance)) {
callback()
}
}
}
}
}
// a simplified version.
/* globals requestAnimationFrame, cancelAnimationFrame */
import { useEffect, useRef } from 'react'
export const useRequestAnimationFrame = renderCallback => {
const requestFrameRef = useRef()
const previousTimeRef = useRef()
const isFirstRenderRef = useRef()
const animate = timestamp => {
if (previousTimeRef.current !== undefined) {
const deltaTime = timestamp - previousTimeRef.current
const isFirstRender = isFirstRenderRef.current === undefined
isFirstRenderRef.current = false
renderCallback({ deltaTime, timestamp, isFirstRender })
}
previousTimeRef.current = timestamp
requestFrameRef.current = requestAnimationFrame(animate)
}
useEffect(() => {
requestFrameRef.current = requestAnimationFrame(animate)
return () => cancelAnimationFrame(requestFrameRef.current)
}, [])
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment