Skip to content

Commit

Permalink
removed unnecessary useEffect & handled content unmount
Browse files Browse the repository at this point in the history
  • Loading branch information
onderonur committed Feb 26, 2023
1 parent dd42b60 commit 3010199
Show file tree
Hide file tree
Showing 4 changed files with 59 additions and 51 deletions.
53 changes: 32 additions & 21 deletions example/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,18 @@ const Top = styled.div`
`;

const Label = styled.label`
display: block;
user-select: none;
font-weight: bold;
> * {
margin-left: 8px;
}
`;

const Checkbox = styled.input.attrs({ type: 'checkbox' })`
margin: 8px 8px 4px 0;
`;

const Scroller = styled.div`
width: 100%;
height: 600px;
Expand Down Expand Up @@ -49,23 +55,23 @@ enum ParentType {
}

function App() {
const [isContentVisible, setIsContentVisible] = React.useState(true);
const [mode, setMode] = React.useState(ParentType.DOCUMENT);
const [ref, { isVisible, rootRef }] = useTrackVisibility();
const [
ref2,
{ isVisible: isVisible2, rootRef: rootRef2 },
] = useTrackVisibility();

const innerContent = (
<Content>
<Ball ref={ref} />
</Content>
);

const innerContent2 = (
<Content>
<Ball ref={ref2} />
</Content>
const content = (
<>
<Content>
<Ball ref={ref} />
</Content>
<Content>
<Ball ref={ref2} />
</Content>
</>
);

const rootCallback = React.useCallback(
Expand Down Expand Up @@ -94,19 +100,24 @@ function App() {
</option>
</select>
</Label>
<Label>
<Checkbox
checked={isContentVisible}
onChange={(e) => setIsContentVisible(e.target.checked)}
/>
Show Content
</Label>
<Message label="First ball" isVisible={isVisible} />
<Message label="Second" isVisible={isVisible2} />
<Message label="Second ball" isVisible={isVisible2} />
</Top>
{mode === ParentType.DOCUMENT ? (
<>
{innerContent}
{innerContent2}
</>
) : (
<Scroller ref={rootCallback}>
{innerContent}
{innerContent2}
</Scroller>
{isContentVisible && (
<div>
{mode === ParentType.DOCUMENT ? (
content
) : (
<Scroller ref={rootCallback}>{content}</Scroller>
)}
</div>
)}
</Root>
);
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "react-intersection-observer-hook",
"version": "2.0.6",
"version": "2.1.0",
"license": "MIT",
"author": "onderonur",
"main": "dist/index.js",
Expand Down
51 changes: 24 additions & 27 deletions src/useIntersectionObserver.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useState, useCallback, useEffect, useRef } from 'react';
import { useState, useCallback, useRef } from 'react';
import { CachedIntersectionObserver, createObserverCache } from './utils';
import { Omit } from './types';

Expand Down Expand Up @@ -49,28 +49,41 @@ function useIntersectionObserver(

const observe = useCallback(() => {
const node = nodeRef.current;
if (node) {
const observer = observerCache.getObserver({
root: rootRef.current,
rootMargin,
threshold,
});
observer.observe(node, (observedEntry) => {
setEntry(observedEntry);
});
observerRef.current = observer;

if (!node) {
setEntry(undefined);
return;
}

const observer = observerCache.getObserver({
root: rootRef.current,
rootMargin,
threshold,
});

observer.observe(node, (observedEntry) => {
setEntry(observedEntry);
});

observerRef.current = observer;
}, [rootMargin, threshold]);

const unobserve = useCallback(() => {
const currentObserver = observerRef.current;
const node = nodeRef.current;

if (node) {
currentObserver?.unobserve(node);
}

observerRef.current = null;
}, []);

// React will call the ref callback with the DOM element when the component mounts,
// and call it with null when it unmounts.
// So, we don't need an useEffect etc to unobserve nodes.
// When nodeRef.current is null, it will be unobserved and observe function
// won't do anything.
const refCallback = useCallback<IntersectionObserverHookRefCallback>(
(node) => {
unobserve();
Expand All @@ -89,22 +102,6 @@ function useIntersectionObserver(
[observe, unobserve],
);

useEffect(() => {
// After React 18, StrictMode unmounts and mounts components to be sure
// if they are resilient effects being mounted and destroyed multiple times.
// This a behavior to be sure nothing breaks when off-screen components
// can preserve their state with future React versions.
// So in StrictMode, React unmounts the component, clean-up of this useEffect gets triggered and
// we stop observing the node. But we need to start observing after component re-mounts with its preserved state.
// So to handle this case, we call initializeObserver here.
// https://reactjs.org/blog/2022/03/08/react-18-upgrade-guide.html#updates-to-strict-mode
observe();
return () => {
// We disconnect the observer on unmount to prevent memory leaks etc.
unobserve();
};
}, [observe, unobserve]);

return [refCallback, { entry, rootRef: rootRefCallback }];
}

Expand Down

0 comments on commit 3010199

Please sign in to comment.