Skip to content

Commit 02ea07f

Browse files
fix: ref cannot be used as a dependency
1 parent cb2569f commit 02ea07f

File tree

4 files changed

+48
-31
lines changed

4 files changed

+48
-31
lines changed

src/scrollama.tsx

+3-3
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ export const Scrollama = <T = unknown>({
1111
debug,
1212
children,
1313
offset = 0.3,
14-
onStepEnter = () => { },
15-
onStepExit = () => { },
16-
onStepProgress = () => { },
14+
onStepEnter,
15+
onStepExit,
16+
onStepProgress,
1717
threshold = 4,
1818
}: ScrollamaProps<T>) => {
1919
const isOffsetDefinedInPixels = isOffsetInPixels(offset);

src/step.tsx

+36-17
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,18 @@
1-
import { useState, useEffect, useMemo, useCallback, useRef, Children, cloneElement, useContext } from 'react';
1+
import {
2+
useState,
3+
useEffect,
4+
useMemo,
5+
useCallback,
6+
useRef,
7+
Children,
8+
cloneElement,
9+
useContext,
10+
useLayoutEffect,
11+
} from 'react';
212
import { useInView } from 'react-intersection-observer';
313

414
import { isBrowser, getRootMargin, getProgressRootMargin } from './utils';
5-
import type { StepProps } from './types';
15+
import type { StepProps, ScrollamaCallbackData } from './types';
616
import { ScrollamaProvide } from './provide';
717

818
export const Step: React.FC<StepProps> = ({
@@ -20,6 +30,7 @@ export const Step: React.FC<StepProps> = ({
2030
innerHeight = 0,
2131
} = useContext(ScrollamaProvide);
2232

33+
const [nodeOffsetHeight, setNodeOffsetHeight] = useState(0);
2334
const rootMargin = getRootMargin({ offset });
2435
const { ref: inViewRef, entry } = useInView({
2536
rootMargin,
@@ -33,9 +44,10 @@ export const Step: React.FC<StepProps> = ({
3344
const ref = useRef<HTMLElement | null>(null);
3445
const [isIntersecting, setIsIntersecting] = useState(false);
3546

47+
3648
const progressRootMargin = useMemo(
37-
() => getProgressRootMargin({ direction, offset, node: ref, innerHeight }),
38-
[direction, offset, ref, innerHeight]
49+
() => getProgressRootMargin({ direction, offset, nodeOffsetHeight, innerHeight }),
50+
[direction, offset, nodeOffsetHeight, innerHeight]
3951
);
4052

4153
const { ref: scrollProgressRef, entry: scrollProgressEntry } = useInView({
@@ -55,7 +67,7 @@ export const Step: React.FC<StepProps> = ({
5567

5668
useEffect(() => {
5769
if (isIntersecting && scrollProgressEntry) {
58-
const { height, top } = scrollProgressEntry.target.getBoundingClientRect();
70+
const { height, top } = scrollProgressEntry.boundingClientRect;
5971
const progress = Math.min(1, Math.max(0, (window.innerHeight * offset - top) / height));
6072
if (onStepProgress) {
6173
onStepProgress({
@@ -70,22 +82,29 @@ export const Step: React.FC<StepProps> = ({
7082
}, [scrollProgressEntry]);
7183

7284
useEffect(() => {
73-
if (entry && !entry.isIntersecting && isIntersecting) {
74-
setIsIntersecting(false);
75-
onStepExit({ element: entry.target, data, entry, direction });
76-
handleSetLastScrollTop(scrollTop)
77-
} else if (entry && entry.isIntersecting && !isIntersecting) {
78-
setIsIntersecting(true);
79-
onStepEnter({ element: entry.target, data, entry, direction });
80-
handleSetLastScrollTop(scrollTop)
85+
if (entry) {
86+
const currentIntersectionState = entry.isIntersecting;
87+
if (currentIntersectionState !== isIntersecting) {
88+
setIsIntersecting(currentIntersectionState);
89+
const eventData: ScrollamaCallbackData<unknown> = { element: entry.target, data, entry, direction };
90+
if (currentIntersectionState) {
91+
onStepEnter(eventData);
92+
} else {
93+
onStepExit(eventData);
94+
}
95+
handleSetLastScrollTop(scrollTop);
96+
}
8197
}
8298
}, [entry]);
8399

100+
useLayoutEffect(() => {
101+
if (ref.current) {
102+
setNodeOffsetHeight(ref.current.offsetHeight);
103+
}
104+
}, [ref.current]);
105+
84106
const childElement = Children.only(children);
85-
return cloneElement(childElement, {
86-
ref: setRefs,
87-
entry,
88-
});
107+
return cloneElement(childElement, { ref: setRefs });
89108
};
90109

91110

src/utils.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ export const getRootMargin = ({ offset }: { offset: number }) => {
5858
interface GetProgressRootMarginParams {
5959
direction: string;
6060
offset: number;
61-
node: React.RefObject<HTMLElement>;
61+
nodeOffsetHeight: number;
6262
innerHeight: number;
6363
}
6464

@@ -68,13 +68,13 @@ interface GetProgressRootMarginParams {
6868
* @param {Object} params - The parameters for calculating the root margin.
6969
* @param {string} params.direction - The scroll direction ('up' or 'down').
7070
* @param {number} params.offset - The offset value, typically between 0 and 1.
71-
* @param {React.RefObject<HTMLElement>} params.node - Reference to the DOM node being tracked.
71+
* @param {number} params.nodeOffsetHeight - The offset height of the node.
7272
* @param {number} params.innerHeight - The inner height of the viewport.
7373
* @returns {string} The calculated root margin string in the format "top right bottom left".
7474
*/
75-
export const getProgressRootMargin = ({ direction, offset, node, innerHeight }: GetProgressRootMarginParams) => {
76-
if (!node.current) return '0px';
77-
const offsetHeight = (node.current.offsetHeight / innerHeight);
75+
export const getProgressRootMargin = ({ direction, offset, nodeOffsetHeight, innerHeight }: GetProgressRootMarginParams) => {
76+
if (!nodeOffsetHeight) return '0px';
77+
const offsetHeight = (nodeOffsetHeight / innerHeight);
7878
if (direction === 'down') return `${(offsetHeight - offset) * 100}% 0px ${(offset * 100) - 100}% 0px`;
7979
return `-${offset * 100}% 0px ${(offsetHeight * 100) - (100 - (offset * 100))}% 0px`;
8080
}

tests/utils.test.ts

+4-6
Original file line numberDiff line numberDiff line change
@@ -72,19 +72,17 @@ describe('getRootMargin', () => {
7272

7373

7474
describe('getProgressRootMargin', () => {
75-
it('should return "0px" when node.current is null', () => {
76-
expect(getProgressRootMargin({ direction: 'down', offset: 0, node: { current: null }, innerHeight: 0 })).toBe('0px');
75+
it('should return "0px" when nodeOffsetHeight is null', () => {
76+
expect(getProgressRootMargin({ direction: 'down', offset: 0, nodeOffsetHeight: 0, innerHeight: 0 })).toBe('0px');
7777
});
7878

7979
it('should return correct margin for downward scrolling', () => {
80-
const mockNode = { current: { offsetHeight: 500 } } as React.RefObject<HTMLElement>;
81-
const result = getProgressRootMargin({ direction: 'down', offset: 0.3, node: mockNode, innerHeight: 1000 });
80+
const result = getProgressRootMargin({ direction: 'down', offset: 0.3, nodeOffsetHeight: 500, innerHeight: 1000 });
8281
expect(result).toBe('20% 0px -70% 0px');
8382
});
8483

8584
it('should return correct margin for upward scrolling', () => {
86-
const mockNode = { current: { offsetHeight: 500 } } as React.RefObject<HTMLElement>;
87-
const result = getProgressRootMargin({ direction: 'up', offset: 0.3, node: mockNode, innerHeight: 1000 });
85+
const result = getProgressRootMargin({ direction: 'up', offset: 0.3, nodeOffsetHeight: 500, innerHeight: 1000 });
8886
expect(result).toBe('-30% 0px -20% 0px');
8987
});
9088
});

0 commit comments

Comments
 (0)