useIntersection
Hook for detection element intersections with viewport or other elements by IntersectionObserver.
import { useIntersection } from '@krutoo/utils/react';
function App() {
const ref = useRef<HTMLDivElement>(null);
useIntersection(ref, entry => {
if (entry.isIntersecting) {
// ...element is in viewport
}
});
return (
<div ref={ref} className='widget'>
Hello, World!
</div>
);
}
Important: threshold and performance
Each known option' changing will provide recreating observer.
For threshold shallow equality check will be used because it can be array.
In case you have threshold as array - try to provide stable array (constant or memoized).
Will work but long arrays can cause performance issues:
// Each render hook will take new array
// So this value is "unstable"
useIntersection(ref, callback, { threshold: [0.1, 0.2, 0.3] });
Right:
// We memoize array, so it is "stable"
const threshold = useMemo(() => [0.1, 0.2, 0.3], []);
// So observer will be created once
useIntersection(ref, callback, { threshold });
Also right:
// We use constant outside component, so it is "stable"
const THRESHOLD = [0.1, 0.2, 0.3];
function App() {
// ...
useIntersection(ref, callback, { threshold: THRESHOLD });
// ...
}
IntersectionObserverContext
Under the hood this hook uses IntersectionObserverContext to create observer.
IntersectionObserverContext by default uses global window.IntersectionObserver.
But using global instance trough context gives you ability to replace implementation whenever you want.
It means that you can:
- provide polyfill implementation for environments that don't support
IntersectionObserver; - provide implementation for SSR;
- provide different implementations for different subtrees of your UI;
- replace instance by mock in your unit tests.
For example next we will use IntersectionObserverMock:
import { useRef } from 'react';
import { IntersectionObserverContext, useIntersection } from '@krutoo/utils/react';
import { IntersectionObserverMock } from '@krutoo/utils/testing';
import { act, expect, render, test } from '#testing';
function MyWidget() {
const ref = useRef(null);
useIntersection(ref, entry => {
ref.current.textContent = entry.isIntersecting ? 'I am on screen' : 'I am hidden';
});
return <div ref={ref} className='my-widget'></div>;
}
test('MyWidget should render message correctly', () => {
let observer;
const context = {
getObserver(...args) {
observer = new IntersectionObserverMock(...args);
return observer;
},
};
const { container } = render(
<IntersectionObserverContext value={context}>
<MyWidget />
</IntersectionObserverContext>,
);
expect(container.textContent).toBe('I am hidden');
act(() => {
observer.simulateIntersection([
{
target: document.querySelector('.my-widget'),
isIntersecting: true,
},
]);
});
expect(container.textContent).toBe('I am on screen');
});
This example shows that you don't need to use environment that implements IntersectionObserver during testing.
So you can use jsdom for example.