Skip to content

Commit 1ab7258

Browse files
Handle elements that aren't mounted (#10)
* Add example to reproduce failure * Implement fix for hook * Work on tests for new behavior * Add test for conditional viewport render * Remove package-lock.json from example * Apply suggestions from code review Co-Authored-By: Mudit Ameta <zeusdeux@gmail.com> * Run prettier on example application * Run prettier on src/index.ts Co-authored-by: Mudit Ameta <zeusdeux@gmail.com>
1 parent 42ae6ba commit 1ab7258

File tree

5 files changed

+153
-5
lines changed

5 files changed

+153
-5
lines changed

cypress/integration/useIntersectionObserver.test.js

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,4 +86,43 @@ describe('useIsInViewport', () => {
8686
expect(window.forwardedViewportRef[0].classList.contains('viewport')).to.be.true
8787
})
8888
})
89+
90+
it('should handle the target not being rendered', () => {
91+
cy.get('[data-testid="toggle-conditional-render-child-test"]').should(
92+
'contain',
93+
'Show conditional render child test'
94+
)
95+
cy.get('[data-testid="toggle-conditional-render-child-test"]').click()
96+
cy.get('[data-testid="toggle-conditional-render-child-test"]').should(
97+
'contain',
98+
'Hide conditional render child test'
99+
)
100+
cy.get('[data-testid="box-status"]').should('contain', 'Out of viewport')
101+
cy.get('[data-testid="toggle-box-hidden"]').click()
102+
cy.get('[data-testid="box-status"]').should('contain', 'In viewport')
103+
cy.get('[data-testid="toggle-box-hidden"]').click()
104+
cy.get('[data-testid="box-status"]').should('contain', 'Out of viewport')
105+
})
106+
107+
it('should handle the viewport not being rendered', () => {
108+
cy.get('[data-testid="toggle-conditional-render-viewport-test"]').should(
109+
'contain',
110+
'Show conditional render viewport test'
111+
)
112+
cy.get('[data-testid="toggle-conditional-render-viewport-test"]').click()
113+
cy.get('[data-testid="toggle-conditional-render-viewport-test"]').should(
114+
'contain',
115+
'Hide conditional render viewport test'
116+
)
117+
cy.get('[data-testid="toggle-viewport-hidden"]').should('contain', 'Show viewport')
118+
cy.get('[data-testid="box-status"]').should('contain', 'Out of viewport')
119+
cy.get('[data-testid="toggle-viewport-hidden"]').click()
120+
cy.get('[data-testid="toggle-viewport-hidden"]').should('contain', 'Hide viewport')
121+
cy.get('[data-testid="box-status"]').should('contain', 'Out of viewport')
122+
cy.get('[data-testid="viewport"]').scrollTo(0, 200)
123+
cy.get('[data-testid="box-status"]').should('contain', 'In viewport')
124+
cy.get('[data-testid="toggle-viewport-hidden"]').click()
125+
cy.get('[data-testid="toggle-viewport-hidden"]').should('contain', 'Show viewport')
126+
cy.get('[data-testid="box-status"]').should('contain', 'Out of viewport')
127+
})
89128
})
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import React from 'react'
2+
import { cx } from 'emotion'
3+
import useIsInViewport from 'use-is-in-viewport'
4+
import {
5+
app,
6+
box,
7+
button,
8+
container,
9+
inWindowViewport,
10+
outsideWindowViewport,
11+
visible
12+
} from './styles'
13+
14+
export function ConditionalChild() {
15+
const [isInViewport, childRef] = useIsInViewport()
16+
const [hidden, toggleHide] = React.useState(true)
17+
18+
return (
19+
<div className={app}>
20+
<button
21+
className={button}
22+
onClick={() => toggleHide(h => !h)}
23+
data-testid="toggle-box-hidden"
24+
>
25+
{hidden ? 'Show box' : 'Hide box'}
26+
</button>
27+
<p data-testid="box-status">{isInViewport ? 'In viewport' : 'Out of viewport'}</p>
28+
{!hidden && (
29+
<div
30+
className={cx(box, {
31+
[inWindowViewport]: !hidden,
32+
[outsideWindowViewport]: hidden,
33+
[visible]: isInViewport
34+
})}
35+
data-testid="box"
36+
ref={childRef}
37+
>
38+
<p>{isInViewport ? 'In viewport' : 'Out of viewport'}</p>
39+
</div>
40+
)}
41+
</div>
42+
)
43+
}
44+
45+
export function ConditionalViewport() {
46+
const [isInViewport, childRef, parentRef] = useIsInViewport()
47+
const [hidden, toggleHide] = React.useState(true)
48+
49+
return (
50+
<div className={app}>
51+
<button
52+
className={button}
53+
onClick={() => toggleHide(h => !h)}
54+
data-testid="toggle-viewport-hidden"
55+
>
56+
{hidden ? 'Show viewport' : 'Hide viewport'}
57+
</button>
58+
<p data-testid="box-status">{isInViewport ? 'In viewport' : 'Out of viewport'}</p>
59+
{!hidden && (
60+
<div ref={parentRef} data-testid="viewport" className={cx('viewport', container)}>
61+
<div
62+
className={cx(box, {
63+
[inWindowViewport]: !hidden,
64+
[outsideWindowViewport]: hidden,
65+
[visible]: isInViewport
66+
})}
67+
data-testid="box"
68+
ref={childRef}
69+
>
70+
<p>{isInViewport ? 'In viewport' : 'Out of viewport'}</p>
71+
</div>
72+
</div>
73+
)}
74+
</div>
75+
)
76+
}

examples/cra/src/index.js

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
SimpleElement as ParentElementViewportSimple,
1010
RefForwardingElement as ParentElementViewportRefForwading
1111
} from './viewportAnotherElement'
12+
import { ConditionalChild, ConditionalViewport } from './conditionalRender'
1213

1314
function App() {
1415
const [testToShow, setTestToShow] = React.useState(1)
@@ -20,40 +21,63 @@ function App() {
2021
window.forwardedViewportRef = window.forwardedViewportRef || []
2122
window.forwardedViewportRef.push(node)
2223
}
24+
const toggleExample = exampleIndex => () => {
25+
setTestToShow(v => {
26+
return v === exampleIndex ? 0 : exampleIndex
27+
})
28+
}
2329

2430
return (
2531
<>
2632
<nav className={nav}>
2733
<button
2834
className={button}
29-
onClick={() => setTestToShow(v => ([0, 2, 3, 4].includes(v) ? 1 : 0))}
35+
onClick={toggleExample(1)}
3036
data-testid="toggle-simple-parent-doc-test"
3137
>
3238
{testToShow !== 1 ? 'Show simple parent doc test' : 'Hide simple parent doc test'}
3339
</button>
3440
<button
3541
className={button}
36-
onClick={() => setTestToShow(v => ([0, 1, 3, 4].includes(v) ? 2 : 0))}
42+
onClick={toggleExample(2)}
3743
data-testid="toggle-ref-forwarding-parent-doc-test"
3844
>
3945
{testToShow !== 2 ? 'Show ref fwd parent doc test' : 'Hide ref fwd parent doc test'}
4046
</button>
4147
<button
4248
className={button}
43-
onClick={() => setTestToShow(v => ([0, 1, 2, 4].includes(v) ? 3 : 0))}
49+
onClick={toggleExample(3)}
4450
data-testid="toggle-simple-parent-element-test"
4551
>
4652
{testToShow !== 3 ? 'Show simple parent element test' : 'Hide simple parent element test'}
4753
</button>
4854
<button
4955
className={button}
50-
onClick={() => setTestToShow(v => ([0, 1, 2, 3].includes(v) ? 4 : 0))}
56+
onClick={toggleExample(4)}
5157
data-testid="toggle-ref-forwarding-parent-element-test"
5258
>
5359
{testToShow !== 4
5460
? 'Show ref fwd parent element test'
5561
: 'Hide ref fwd parent element test'}
5662
</button>
63+
<button
64+
className={button}
65+
onClick={toggleExample(5)}
66+
data-testid="toggle-conditional-render-child-test"
67+
>
68+
{testToShow !== 5
69+
? 'Show conditional render child test'
70+
: 'Hide conditional render child test'}
71+
</button>
72+
<button
73+
className={button}
74+
onClick={toggleExample(6)}
75+
data-testid="toggle-conditional-render-viewport-test"
76+
>
77+
{testToShow !== 6
78+
? 'Show conditional render viewport test'
79+
: 'Hide conditional render viewport test'}
80+
</button>
5781
</nav>
5882
{testToShow !== 1 ? null : <DocViewportSimple />}
5983
{testToShow !== 2 ? null : (
@@ -63,6 +87,8 @@ function App() {
6387
{testToShow !== 4 ? null : (
6488
<ParentElementViewportRefForwading ref={forwardedViewportRef} threshold={75} />
6589
)}
90+
{testToShow !== 5 ? null : <ConditionalChild />}
91+
{testToShow !== 6 ? null : <ConditionalViewport />}
6692
</>
6793
)
6894
}

examples/cra/src/styles.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ export const app = css`
2525
position: fixed;
2626
margin-top: 51px;
2727
}
28+
& > p {
29+
position: fixed;
30+
margin-top: 120px;
31+
}
2832
`
2933

3034
export const nav = css`

src/index.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ export default function useIsInViewport(
4646
)
4747

4848
useEffect(() => {
49+
if (!childRef.current) {
50+
return
51+
}
4952
return observeElementInViewport(
5053
childRef.current,
5154
() => setIsInViewport(true),
@@ -55,7 +58,7 @@ export default function useIsInViewport(
5558
viewport: parentRef.current
5659
}
5760
)
58-
}, [childRef, restOpts, parentRef])
61+
}, [childRef.current, restOpts, parentRef])
5962

6063
return [isInViewport, childCbRef, parentCbRef]
6164
}

0 commit comments

Comments
 (0)