Skip to content

Instantly share code, notes, and snippets.

@kpunith8
Created April 29, 2022 04:47
Show Gist options
  • Save kpunith8/a17a86cfc13e4081ab81dc59515d4536 to your computer and use it in GitHub Desktop.
Save kpunith8/a17a86cfc13e4081ab81dc59515d4536 to your computer and use it in GitHub Desktop.
Custom scroll hooks - refer react-use library - unit tests with RTL
import {useEffect} from 'react'
import globalProxy from '../../lib/global-proxy'
import useRafState from './use-raf-state'
// Adopted from https://github.com/streamich/react-use/blob/master/src/useWindowScroll.ts
export const useWindowScroll = () => {
const [state, setState] = useRafState(() => ({
x: globalProxy().window.pageXOffset,
y: globalProxy().window.pageYOffset,
}))
useEffect(() => {
const scrollHandler = () => {
setState(prevState => {
const {pageXOffset, pageYOffset} = globalProxy().window
// Check state for change, return same state if no change happened to prevent rerender
// (see useState/setState documentation). useState/setState is used internally in useRafState/setState.
return prevState.x !== pageXOffset || prevState.y !== pageYOffset
? {
x: pageXOffset,
y: pageYOffset,
}
: prevState
})
}
// We have to update window scroll at mount, before subscription.
// Window scroll may be changed between render and effect handler.
scrollHandler()
globalProxy().window.addEventListener('scroll', scrollHandler, {
capture: false,
passive: true,
})
return () => {
globalProxy().window.removeEventListener('scroll', scrollHandler)
}
}, [setState])
return state
}
// Adopted from https://github.com/streamich/react-use/blob/master/src/useScroll.ts
export const useScroll = ref => {
const [state, setState] = useRafState({x: 0, y: 0})
useEffect(() => {
const scrollRef = ref.current
const scrollHandler = () => {
if (scrollRef) {
setState({x: scrollRef.scrollLeft, y: scrollRef.scrollTop})
}
}
if (scrollRef) {
scrollRef.addEventListener('scroll', scrollHandler, {
capture: false,
passive: true,
})
}
return () => {
if (scrollRef) {
scrollRef.removeEventListener('scroll', scrollHandler)
}
}
}, [ref, setState])
return state
}
import {useRef} from 'react'
import {useScroll, useWindowScroll} from './scroll-hooks'
import * as useRafState from './use-raf-state'
describe(__filename, () => {
describe('useScroll', () => {
let sandbox
let find
let yOffset = 0
const DemoContainer = () => {
const containerRef = useRef(null)
const {y} = useScroll(containerRef)
return (
<div
className="demo-container"
ref={containerRef}
css={{width: '100%', height: 400}}
>
<div
className="scroll-container"
css={{height: 300, overflowY: 'scroll'}}
>
Test content
</div>
<button className="submit-button" type="button" disabled={y < 100}>
Submit
</button>
</div>
)
}
beforeEach(() => {
sandbox = sinon.createSandbox()
sandbox
.stub(useRafState, 'default')
.value(() => [{x: 0, y: yOffset}, sinon.spy()])
;({find} = render(<DemoContainer />))
})
afterEach(() => {
sandbox.restore()
})
it('should disable the submit button', () => {
expect(find('.submit-button')).to.be.disabled()
})
context('when the yOffset updated', () => {
before(() => (yOffset = 120))
after(() => (yOffset = 0))
it('should enable the submit button', () => {
expect(find('.submit-button')).not.to.be.disabled()
})
})
})
describe('useWindowScroll', () => {
let sandbox
let find
let yOffset = 0
const DemoContainer = () => {
const {y} = useWindowScroll()
return <div className={y > 0 && 'sticky')} />
}
beforeEach(() => {
sandbox = sinon.createSandbox()
sandbox
.stub(useRafState, 'default')
.value(() => [{x: 0, y: yOffset}, sinon.spy()])
;({find} = render(<DemoContainer />))
})
afterEach(() => {
sandbox.restore()
})
it('should not have a sticky class', () => {
expect(find('.sticky')).not.to.be.ok
})
context('when the yOffset updated', () => {
before(() => (yOffset = 10))
after(() => (yOffset = 0))
it('should have a sticky class', () => {
expect(find('.sticky')).to.be.ok
})
})
})
})
import {useEffect, useRef, useState, useCallback} from 'react'
// Adopted from https://github.com/streamich/react-use/blob/master/src/useRafState.ts
const useRafState = initialState => {
const frame = useRef(0)
const [state, setState] = useState(initialState)
const setRafState = useCallback(value => {
global.cancelAnimationFrame(frame.current)
frame.current = global.requestAnimationFrame(() => {
setState(value)
})
}, [])
useEffect(() => () => global.cancelAnimationFrame(frame.current), [])
return [state, setRafState]
}
export default useRafState
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment