import * as React from 'react';
import { InViewHookResponse, IntersectionOptions } from './index';
import { useEffect } from 'react';
import { observe } from './observers';
type State = {
inView: boolean;
entry?: IntersectionObserverEntry;
};
/**
* React Hooks make it easy to monitor the `inView` state of your components. Call
* the `useInView` hook with the (optional) [options](#options) you need. It will
* return an array containing a `ref`, the `inView` status and the current
* [`entry`](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserverEntry).
* Assign the `ref` to the DOM element you want to monitor, and the hook will
* report the status.
*
* @example
* ```jsx
* import React from 'react';
* import { useInView } from 'react-intersection-observer';
*
* const Component = () => {
* const { ref, inView, entry } = useInView({
* threshold: 0,
* });
*
* return (
*
*
{`Header inside viewport ${inView}.`}
*
* );
* };
* ```
*/
export function useInView({
threshold,
delay,
trackVisibility,
rootMargin,
root,
triggerOnce,
skip,
initialInView,
}: IntersectionOptions = {}): InViewHookResponse {
const unobserve = React.useRef();
const [state, setState] = React.useState({
inView: !!initialInView,
});
const setRef = React.useCallback(
(node) => {
if (unobserve.current !== undefined) {
unobserve.current();
unobserve.current = undefined;
}
// Skip creating the observer
if (skip) return;
if (node) {
unobserve.current = observe(
node,
(inView, entry) => {
setState({ inView, entry });
if (entry.isIntersecting && triggerOnce && unobserve.current) {
// If it should only trigger once, unobserve the element after it's inView
unobserve.current();
unobserve.current = undefined;
}
},
{
root,
rootMargin,
threshold,
// @ts-ignore
trackVisibility,
// @ts-ignore
delay,
},
);
}
},
// We break the rule here, because we aren't including the actual `threshold` variable
// eslint-disable-next-line react-hooks/exhaustive-deps
[
// If the threshold is an array, convert it to a string so it won't change between renders.
// eslint-disable-next-line react-hooks/exhaustive-deps
Array.isArray(threshold) ? threshold.toString() : threshold,
root,
rootMargin,
triggerOnce,
skip,
trackVisibility,
delay,
],
);
/* eslint-disable-next-line */
useEffect(() => {
if (!unobserve.current && state.entry && !triggerOnce && !skip) {
// If we don't have a ref, then reset the state (unless the hook is set to only `triggerOnce` or `skip`)
// This ensures we correctly reflect the current state - If you aren't observing anything, then nothing is inView
setState({
inView: !!initialInView,
});
}
});
const result = [setRef, state.inView, state.entry] as InViewHookResponse;
// Support object destructuring, by adding the specific values.
result.ref = result[0];
result.inView = result[1];
result.entry = result[2];
return result;
}