Skip to content

Commit

Permalink
Updates to useIntersectionObserver
Browse files Browse the repository at this point in the history
  • Loading branch information
imbhargav5 committed Sep 18, 2019
1 parent 117566f commit ca41e31
Show file tree
Hide file tree
Showing 3 changed files with 103 additions and 175 deletions.
13 changes: 5 additions & 8 deletions packages/intersectionobserver/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,13 @@ import useIntersectionObserver from "@rooks/use-intersectionobserver"

```jsx
function IntersectionObserverApp() {

const boxRef = React.useRef(null);
const documentRef = React.useRef(null);

/**
* Optional arguments:
**/

/**
* rootRef: ref to the parent dom element with which respect to, the visibility will be checked,
* root: element with respect to which the visibility needs to be checked,
* By default document is considered
*/

Expand Down Expand Up @@ -68,10 +66,9 @@ function IntersectionObserverApp() {
* which will tell hook to observe the list item relative to ul container.
*/
const option = {
elementRef: boxRef
/* rootRef: documentRef, */
/* root: viewport, */
/* rootMargin: "0px 0px 0px 0px", */
/* threshold: "0, 0.5, 1", */
/* threshold: [0, 0.5, 1], */

This comment has been minimized.

Copy link
@simbathesailor

simbathesailor Sep 18, 2019

Contributor

Cool, I was thinking to discuss this. But any ways, now the API is similar to native IntersectionObserver

/* when: true, */
/* callback can also be passed
callback: () => {
Expand All @@ -80,7 +77,7 @@ function IntersectionObserverApp() {
/* visibilityCondition */
};
// const option = useOptionCreator(boxRef, documentRef);
const [isVisible, intersectionObj, observerInState] = useIntersectionObserver(option);
const [boxRef, isVisible, intersectionObj, observerInState] = useIntersectionObserver(option);

This comment has been minimized.

Copy link
@simbathesailor

simbathesailor Sep 18, 2019

Contributor

It is nice, I haven't thought about refs this way.

return (
<div className="App" style={
{ "display": "flex", "flexDirection": "column", "alignItems": "center" }
Expand Down
156 changes: 71 additions & 85 deletions packages/intersectionobserver/src/useIntersectionObserver.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as React from "react";
import { useDidMount } from "shared/useDidMount";

const initialState = {
intersectionObj: {},
Expand All @@ -11,7 +12,6 @@ interface Iaction {
data: any;
}


function IntersectionObserverReducer(state: any, action: Iaction) {
switch (action.type) {
case "SETINTERSECTIONOBJ": {
Expand Down Expand Up @@ -46,16 +46,16 @@ const checkFeasibility = () => {
return true;
};
interface IOptions {
elementRef: React.MutableRefObject<HTMLElement>;
rootRef?: React.MutableRefObject<HTMLElement>;
root: HTMLElement;
rootMargin?: string;
threshold?: string;
threshold?: number[];
when?: boolean;
callback?: Function;
visibilityCondition?: (entry: IntersectionObserverEntry) => boolean;
}

type useIntersectionObserverReturn = [
(node: HTMLElement) => void,
boolean,
IntersectionObserverEntry,
IntersectionObserver
Expand Down Expand Up @@ -89,125 +89,111 @@ function useIntersectionObserver(
): useIntersectionObserverReturn {
const defaultOptions = {
rootMargin: "0px 0px 0px 0px",
threshold: "0, 1",
threshold: [0, 1],
when: true,
visibilityCondition: defaultVisibilityCondition,
rootRef: React.useRef(null)
visibilityCondition: defaultVisibilityCondition
};

const {
elementRef,
rootRef,
root = null,
rootMargin,
threshold,
when,
callback,
visibilityCondition
} = { ...defaultOptions, ...options };

const [element, setElement] = React.useState<HTMLElement>(null);

const [state, dispatch] = React.useReducer(
IntersectionObserverReducer,
initialState
);
const { intersectionObj, observerInState, isVisible } = state;
const observerRef = React.useRef(null);
const callbackRef = React.useRef(null);
const savedCallbackRef = React.useRef(callback);
const visibilityConditionRef = React.useRef(visibilityCondition);

React.useEffect(() => {
visibilityConditionRef.current = visibilityCondition;
});
React.useEffect(() => {
savedCallbackRef.current = callback;
});

useDidMount(() => {
checkFeasibility();
});

This comment has been minimized.

Copy link
@simbathesailor

simbathesailor Sep 18, 2019

Contributor

Hi @imbhargav5 ,

WIll this prevent other effects to run ? How It will prevent it?

This comment has been minimized.

Copy link
@imbhargav5

imbhargav5 Sep 20, 2019

Author Owner

We don't need to check feasiiblity on every run right? We just need to warn the user once. Let me know if I am missing anything.


/**
* Setting callback Ref
*/

React.useEffect(() => {
if (!checkFeasibility()) {
return;
}
if (!callback) {
let callbackDefault = function(
entries: IntersectionObserverEntry[],
observer: IntersectionObserver
) {
entries.forEach((entry: IntersectionObserverEntry) => {
// setIntersectionObj(entry);
dispatch({
type: "SETINTERSECTIONOBJ",
data: entry
});
if (!observerInState) {
dispatch({
type: "SETOBSERVERHANDLE",
data: observer
});
// setObserver(observer);
}
let finalVisibilityFunction = defaultVisibilityCondition;
if (visibilityCondition) {
finalVisibilityFunction = visibilityCondition;
}
const handleIntersectionObserve = React.useCallback(
(entries: IntersectionObserverEntry[], observer: IntersectionObserver) => {
entries.forEach((entry: IntersectionObserverEntry) => {
// setIntersectionObj(entry);
dispatch({
type: "SETINTERSECTIONOBJ",
data: entry
});
if (!observerInState) {
dispatch({
type: "SET_VISIBILITY",
data: finalVisibilityFunction(entry)
type: "SETOBSERVERHANDLE",
data: observer
});

// Each entry describes an intersection change for one observed
// target element:
// entry.boundingClientRect
// entry.intersectionRatio
// entry.intersectionRect
// entry.isIntersecting
// entry.rootBounds
// entry.target
// entry.time
// setObserver(observer);
}
const _visibilityFn =
visibilityConditionRef.current || defaultVisibilityCondition;
dispatch({
type: "SET_VISIBILITY",
data: _visibilityFn(entry)
});
};
callbackRef.current = callbackDefault;
} else {
callbackRef.current = callback;
}
}, [callback, observerInState, visibilityCondition]);

/**
* unobserving intersectionobserver when "when" key is false
*/
React.useEffect(() => {
if (!checkFeasibility()) {
return;
}
if (!when) {
if (observerRef.current) {
observerRef.current.unobserve(elementRef.current);
}
}
}, [observerRef, elementRef, rootRef, rootMargin, when, callback]);
// Each entry describes an intersection change for one observed
// target element:
// entry.boundingClientRect
// entry.intersectionRatio
// entry.intersectionRect
// entry.isIntersecting
// entry.rootBounds
// entry.target
// entry.time
});
savedCallbackRef.current && savedCallbackRef.current();
},
[observerInState]
);

/**
* Effect responsible for creating intersection observer and
* registering the observer for specific element
*/
React.useEffect(() => {
if (!checkFeasibility()) {
return;
}
const currentELem = elementRef.current;
const currentRootElem = rootRef.current;
if (when) {
let observer = new IntersectionObserver(callbackRef.current, {
root: currentRootElem,
threshold: threshold.split(",").map(elem => parseFloat(elem)),
const observer = new IntersectionObserver(handleIntersectionObserve, {
root,
threshold,
rootMargin
});
observerRef.current = observer;

if (currentELem && observerRef.current) {
observerRef.current.observe(currentELem);
if (element && observerRef.current) {
observerRef.current.observe(element);
return () => {
if (element && observerRef.current) {
observerRef.current.unobserve(element);
}
};
}
}
return () => {
if (currentELem) {
observerRef.current.unobserve(currentELem);
}
};
}, [elementRef, rootRef, rootMargin, when, callbackRef, threshold]);
}, [rootMargin, when, savedCallbackRef, threshold]);

const callbackRef = React.useCallback((node: HTMLElement) => {
setElement(node);
}, []);

return [isVisible, intersectionObj, observerInState];
return [callbackRef, isVisible, intersectionObj, observerInState];
}

export { useIntersectionObserver };
109 changes: 27 additions & 82 deletions packages/storybook/src/intersectionobserver.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,100 +9,45 @@ storiesOf("useIntersectionObserver", module)
sidebar: README
}
})
.add("Basic intersection observer example", () => <IntersectionObserverApp />)
.add("Basic intersection observer example", () => (
<IntersectionObserverApp />
));

/***
* To use the the intersection Observer
* visibiltyCondition call back can sent , which will be having access to
* intersection entry object
* Read https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API
* about various attributes provided by entries
* Each entry describes an intersection change for one observed
* target element:
* entry.boundingClientRect
* entry.intersectionRatio
* entry.intersectionRect
* entry.isIntersecting
* entry.rootBounds
* entry.target
* entry.time
*/
const visibilityCondition = (entry) => {
const visibilityCondition = entry => {
if (entry.intersectionRatio >= 0.5) {
return true;
}
return false;
};
function IntersectionObserverApp() {

const boxRef = React.useRef(null);
const documentRef = React.useRef(null);
/**
* Optional arguments:
**/

/**
* rootRef: ref to the parent dom element with which respect to, the visibility will be checked,
* By default document is considered
*/

/**
* rootMargin: serves to grow or shrink each side of the root element's bounding box before computing intersections
* Read more about this here: https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API#The_root_element_and_root_margin
*/

/**
* threshold: indicates at what percentage of the target's visibility the observer's callback should be executed.
* Read more about it here: https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API#The_root_element_and_root_margin
*/

/**
* when: By default true, when made false observer will stop observing the element
*/

/**
* callback: A function callback which will get IntersectionObserverEntry[] and IntersectionObserver as the first and second arguments
*/

/**
* visibilityCondition: This function will get the entry object as the first and only argument, which can be
* used to write custom logic which returns a boolean telling whether thos entry can be said as visible or not.
*/

/**
* For further information, read https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API
*/

/**
* Using the API requires only one mandatory option, ref to the element that need to observed.
* In scenarios, like when we have list of items (<li>) in parent element <ul>, and user need to figure out
* when the list item is visible in ul container, the user need to pass the rootRef attribute(ul element ref) as option
* which will tell hook to observe the list item relative to ul container.
*/
const option = {
elementRef: boxRef
/* rootRef: documentRef, */
/* rootMargin: "0px 0px 0px 0px", */
/* threshold: "0, 0.5, 1", */
/* when: true, */
/* callback can also be passed
callback: () => {
console.log("threshold got hit");
}, */
/* visibilityCondition */
};
// const option = useOptionCreator(boxRef, documentRef);
const [isVisible, intersectionObj, observerInState] = useIntersectionObserver(option);
const [
elementRef,
isVisible,
intersectionObj,
observerInState
] = useIntersectionObserver();
return (
<div className="App" style={
{ "display": "flex", "flexDirection": "column", "alignItems": "center" }
}>
<div
className="App"
style={{ display: "flex", flexDirection: "column", alignItems: "center" }}
>
<h1>See for the visibility of box at bottom of page</h1>
<h2>Start scroling down to the visibility change!</h2>
<div ref={boxRef} className="box" style={{ "position": "relative", "top": "1200px", "height": "200px", "width": "200px", "marginBottom": "20px", "backgroundColor": "aqua" }}>
<div
ref={elementRef}
className="box"
style={{
position: "relative",
top: "1200px",
height: "200px",
width: "200px",
marginBottom: "20px",
backgroundColor: "aqua"
}}
>
{isVisible ? "Box is visible" : "Box is not visible"}
</div>
{isVisible ? "Box is visible" : "Box is not visible"}
</div>
);
}
}

0 comments on commit ca41e31

Please sign in to comment.