Skip to content

Commit 6b4bd6d

Browse files
authored
feat: support skip option (#363)
1 parent 1e10ee9 commit 6b4bd6d

File tree

7 files changed

+62
-11
lines changed

7 files changed

+62
-11
lines changed

README.md

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -145,12 +145,13 @@ export default Component
145145
Provide these as props on the **`<InView />`** component and as the options
146146
argument for the hooks.
147147

148-
| Name | Type | Default | Required | Description |
149-
| --------------- | ------------------ | ------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
150-
| **root** | Element | document | false | The IntersectionObserver interface's read-only root property identifies the Element or Document whose bounds are treated as the bounding box of the viewport for the element which is the observer's target. If the root is null, then the bounds of the actual document viewport are used. |
151-
| **rootMargin** | string | '0px' | false | Margin around the root. Can have values similar to the CSS margin property, e.g. "10px 20px 30px 40px" (top, right, bottom, left). |
152-
| **threshold** | number \| number[] | 0 | false | Number between 0 and 1 indicating the percentage that should be visible before triggering. Can also be an array of numbers, to create multiple trigger points. |
153-
| **triggerOnce** | boolean | false | false | Only trigger this method once |
148+
| Name | Type | Default | Required | Description |
149+
| --------------- | ------------------ | -------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
150+
| **root** | Element | document | false | The IntersectionObserver interface's read-only root property identifies the Element or Document whose bounds are treated as the bounding box of the viewport for the element which is the observer's target. If the root is null, then the bounds of the actual document viewport are used. |
151+
| **rootMargin** | string | '0px' | false | Margin around the root. Can have values similar to the CSS margin property, e.g. "10px 20px 30px 40px" (top, right, bottom, left). |
152+
| **threshold** | number \| number[] | 0 | false | Number between 0 and 1 indicating the percentage that should be visible before triggering. Can also be an array of numbers, to create multiple trigger points. |
153+
| **skip** | boolean | false | false | Skip creating the IntersectionObserver. You can use this to enable and disable the observer as needed. If `skip` is set while `inView`, the current state will still be kept. |
154+
| **triggerOnce** | boolean | false | false | Only trigger this method once. |
154155

155156
> ⚠️ When passing an array to `threshold`, store the array in a constant to
156157
> avoid the component re-rendering too often. For example:

src/InView.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ export class InView extends React.Component<
7474
node: Element | null = null
7575

7676
observeNode() {
77-
if (!this.node) return
77+
if (!this.node || this.props.skip) return
7878
const { threshold, root, rootMargin } = this.props
7979
observe(this.node, this.handleChange, {
8080
threshold,

src/__tests__/Observer.test.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,12 @@ it('Should render <Observer /> render when in view', () => {
7777
getByText('Inview: true')
7878
})
7979

80+
it('Should respect skip', () => {
81+
render(<Observer skip>{plainChild}</Observer>)
82+
83+
expect(observe).not.toHaveBeenCalled()
84+
})
85+
8086
it('Should unobserve old node', () => {
8187
const { rerender, container } = render(
8288
<Observer>

src/__tests__/hooks.test.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,27 @@ test('should respect trigger once', () => {
6767
getByText('true')
6868
})
6969

70+
test('should respect skip', () => {
71+
const { getByText, rerender } = render(
72+
<HookComponent options={{ skip: true }} />,
73+
)
74+
mockAllIsIntersecting(false)
75+
getByText('false')
76+
77+
rerender(<HookComponent options={{ skip: false }} />)
78+
mockAllIsIntersecting(true)
79+
getByText('true')
80+
})
81+
82+
test('should not reset current state if changing skip', () => {
83+
const { getByText, rerender } = render(
84+
<HookComponent options={{ skip: false }} />,
85+
)
86+
mockAllIsIntersecting(true)
87+
rerender(<HookComponent options={{ skip: true }} />)
88+
getByText('true')
89+
})
90+
7091
test('should unmount the hook', () => {
7192
const { unmount, getByTestId } = render(<HookComponent />)
7293
const wrapper = getByTestId('wrapper')

src/index.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ interface RenderProps {
3030
export interface IntersectionOptions extends IntersectionObserverInit {
3131
/** Only trigger the inView callback once */
3232
triggerOnce?: boolean
33+
/** Skip assigning the observer to the `ref` */
34+
skip?: boolean
3335
}
3436

3537
export interface IntersectionObserverProps extends IntersectionOptions {

src/stories/Hooks.story.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,3 +124,9 @@ export const triggerOnce = () => (
124124
<HookComponent options={{ triggerOnce: true }} />
125125
</ScrollWrapper>
126126
)
127+
128+
export const skip = () => (
129+
<ScrollWrapper>
130+
<HookComponent options={{ skip: true }} />
131+
</ScrollWrapper>
132+
)

src/useInView.tsx

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@ export function useInView(
2626
unobserve(ref.current)
2727
}
2828

29+
if (options.skip) {
30+
ref.current = undefined
31+
return
32+
}
33+
2934
if (node) {
3035
observe(
3136
node,
@@ -40,16 +45,26 @@ export function useInView(
4045
options,
4146
)
4247
}
43-
4448
// Store a reference to the node, so we can unobserve it later
4549
ref.current = node
4650
},
47-
[options.threshold, options.root, options.rootMargin, options.triggerOnce],
51+
[
52+
options.threshold,
53+
options.root,
54+
options.rootMargin,
55+
options.triggerOnce,
56+
options.skip,
57+
],
4858
)
4959

5060
useEffect(() => {
51-
if (!ref.current && state !== initialState && !options.triggerOnce) {
52-
// If we don't have a ref, then reset the state (unless the hook is set to only `triggerOnce`)
61+
if (
62+
!ref.current &&
63+
state !== initialState &&
64+
!options.triggerOnce &&
65+
!options.skip
66+
) {
67+
// If we don't have a ref, then reset the state (unless the hook is set to only `triggerOnce` or `skip`)
5368
// This ensures we correctly reflect the current state - If you aren't observing anything, then nothing is inView
5469
setState(initialState)
5570
}

0 commit comments

Comments
 (0)