Skip to content

Commit 99b7234

Browse files
committed
feat: stop measures and reactions on unmount
1 parent 7b8e621 commit 99b7234

File tree

5 files changed

+50
-49
lines changed

5 files changed

+50
-49
lines changed

src/components/LazyChild/components/FullLazyChild.tsx

Lines changed: 23 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useCallback, useEffect } from 'react';
1+
import React, { useCallback } from 'react';
22
import { Dimensions, type LayoutChangeEvent } from 'react-native';
33
import Animated, {
44
measure,
@@ -10,10 +10,10 @@ import Animated, {
1010
useSharedValue,
1111
} from 'react-native-reanimated';
1212
import { useAnimatedContext } from '../../../context/AnimatedContext';
13+
import { logger } from '../../../utils/logger';
1314
import { useEnteringCallbacks } from '../hooks/useEnteringCallbacks';
1415
import { useVisibilityCallbacks } from '../hooks/useVisibilityCallbacks';
1516
import { LazyChildProps } from '../types';
16-
import { logger } from '../../../utils/logger';
1717

1818
const SCREEN_HEIGHT = Dimensions.get('window').height;
1919

@@ -37,18 +37,9 @@ export function FullLazyChild({
3737
containerStart,
3838
containerEnd,
3939
horizontal,
40+
isScrollUnmounted,
4041
} = useAnimatedContext();
4142

42-
const _isMounted = useSharedValue(false);
43-
44-
useEffect(() => {
45-
_isMounted.value = true;
46-
47-
return () => {
48-
_isMounted.value = false;
49-
};
50-
}, [_isMounted]);
51-
5243
/**
5344
* If onLayout returns a height or width value greater than 0.
5445
*/
@@ -64,25 +55,25 @@ export function FullLazyChild({
6455

6556
const _debug = useSharedValue(debug);
6657

67-
const _shouldFireThresholdEnter = useSharedValue(
68-
typeof onEnterThresholdPass === 'function'
69-
);
70-
const _shouldFireThresholdExit = useSharedValue(
71-
typeof onExitThresholdPass === 'function'
72-
);
73-
const _shouldMeasurePercentVisible = useSharedValue(
74-
typeof onVisibilityEnter === 'function'
75-
);
76-
const _shouldFireVisibilityExit = useSharedValue(
77-
typeof onVisibilityExit === 'function'
78-
);
58+
const shouldFireThresholdEnter = useDerivedValue(() => {
59+
return typeof onEnterThresholdPass === 'function' && !isScrollUnmounted;
60+
});
61+
const shouldFireThresholdExit = useDerivedValue(() => {
62+
return typeof onExitThresholdPass === 'function' && !isScrollUnmounted;
63+
});
64+
const shouldMeasurePercentVisible = useDerivedValue(() => {
65+
return typeof onVisibilityEnter === 'function' && !isScrollUnmounted;
66+
});
67+
const shouldFireVisibilityExit = useDerivedValue(() => {
68+
return typeof onVisibilityExit === 'function' && !isScrollUnmounted;
69+
});
7970

8071
const _hasValidCallback = useDerivedValue(
8172
() =>
82-
_shouldFireThresholdEnter.value ||
83-
_shouldFireThresholdExit.value ||
84-
_shouldMeasurePercentVisible.value ||
85-
_shouldFireVisibilityExit.value
73+
shouldFireThresholdEnter.value ||
74+
shouldFireThresholdExit.value ||
75+
shouldMeasurePercentVisible.value ||
76+
shouldFireVisibilityExit.value
8677
);
8778

8879
function measureView() {
@@ -123,8 +114,8 @@ export function FullLazyChild({
123114
onEnterThresholdPass,
124115
onExitThresholdPass,
125116
_measurement,
126-
_shouldFireThresholdEnter,
127-
_shouldFireThresholdExit,
117+
shouldFireThresholdEnter,
118+
shouldFireThresholdExit,
128119
startTrigger,
129120
endTrigger,
130121
horizontal,
@@ -134,8 +125,8 @@ export function FullLazyChild({
134125
percentVisibleThreshold,
135126
onVisibilityEnter,
136127
onVisibilityExit,
137-
_shouldMeasurePercentVisible,
138-
_shouldFireVisibilityExit,
128+
shouldMeasurePercentVisible,
129+
shouldFireVisibilityExit,
139130
_measurement,
140131
containerStart,
141132
containerEnd,

src/components/LazyChild/hooks/useEnteringCallbacks.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,17 @@ export const useEnteringCallbacks = ({
1212
onEnterThresholdPass,
1313
onExitThresholdPass,
1414
_measurement,
15-
_shouldFireThresholdEnter,
16-
_shouldFireThresholdExit,
15+
shouldFireThresholdEnter,
16+
shouldFireThresholdExit,
1717
startTrigger,
1818
endTrigger,
1919
horizontal,
2020
}: {
2121
onEnterThresholdPass?: () => void;
2222
onExitThresholdPass?: () => void;
2323
_measurement: SharedValue<ReturnType<typeof measure>>;
24-
_shouldFireThresholdEnter: SharedValue<boolean>;
25-
_shouldFireThresholdExit: SharedValue<boolean>;
24+
shouldFireThresholdEnter: SharedValue<boolean>;
25+
shouldFireThresholdExit: SharedValue<boolean>;
2626
startTrigger: Pick<SharedValue<number>, 'value'>;
2727
endTrigger: Pick<SharedValue<number>, 'value'>;
2828
horizontal: Pick<SharedValue<boolean>, 'value'>;
@@ -35,9 +35,9 @@ export const useEnteringCallbacks = ({
3535
_hasFiredThresholdEntered.value = true;
3636
_hasFiredThresholdExited.value = false;
3737

38-
if (!_shouldFireThresholdExit.value) {
38+
if (!shouldFireThresholdExit.value) {
3939
// Enter callback has fired and there is no exit callback, so it cannot refire. Set shouldFire to false to prevent unnecessary measures.
40-
_shouldFireThresholdEnter.value = false;
40+
shouldFireThresholdEnter.value = false;
4141
}
4242

4343
onEnterThresholdPass();
@@ -78,11 +78,11 @@ export const useEnteringCallbacks = ({
7878
() => _hasEntered.value,
7979
(hasLazyChildEntered) => {
8080
if (hasLazyChildEntered) {
81-
if (_shouldFireThresholdEnter.value) {
81+
if (shouldFireThresholdEnter.value) {
8282
runOnJS(handleThresholdEntered)();
8383
}
8484
} else {
85-
if (_shouldFireThresholdExit.value) {
85+
if (shouldFireThresholdExit.value) {
8686
runOnJS(handleOnThresholdExited)();
8787
}
8888
}

src/components/LazyChild/hooks/useVisibilityCallbacks.tsx

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ interface Props {
1212
percentVisibleThreshold: number;
1313
onVisibilityEnter?: () => void;
1414
onVisibilityExit?: () => void;
15-
_shouldMeasurePercentVisible: SharedValue<boolean>;
16-
_shouldFireVisibilityExit: SharedValue<boolean>;
15+
shouldMeasurePercentVisible: SharedValue<boolean>;
16+
shouldFireVisibilityExit: SharedValue<boolean>;
1717
_measurement: SharedValue<ReturnType<typeof measure>>;
1818
containerStart: Pick<SharedValue<number>, 'value'>;
1919
containerEnd: Pick<SharedValue<number>, 'value'>;
@@ -24,8 +24,8 @@ export const useVisibilityCallbacks = ({
2424
percentVisibleThreshold,
2525
onVisibilityEnter,
2626
onVisibilityExit,
27-
_shouldMeasurePercentVisible,
28-
_shouldFireVisibilityExit,
27+
shouldMeasurePercentVisible,
28+
shouldFireVisibilityExit,
2929
_measurement,
3030
containerStart,
3131
containerEnd,
@@ -41,9 +41,9 @@ export const useVisibilityCallbacks = ({
4141
_hasFiredOnVisibilityEntered.value = true;
4242
_hasFiredOnVisibilityExited.value = false;
4343

44-
if (!_shouldFireVisibilityExit.value) {
44+
if (!shouldFireVisibilityExit.value) {
4545
// Enter callback has fired and there is no exit callback, so it cannot refire. Set shouldFire to false to prevent unnecessary measures.
46-
_shouldMeasurePercentVisible.value = false;
46+
shouldMeasurePercentVisible.value = false;
4747
}
4848

4949
onVisibilityEnter();
@@ -96,11 +96,11 @@ export const useVisibilityCallbacks = ({
9696
() => _isVisible.value,
9797
(isLazyChildVisible) => {
9898
if (isLazyChildVisible) {
99-
if (_shouldMeasurePercentVisible.value) {
99+
if (shouldMeasurePercentVisible.value) {
100100
runOnJS(handleOnVisibilityEntered)();
101101
}
102102
} else {
103-
if (_shouldFireVisibilityExit.value) {
103+
if (shouldFireVisibilityExit.value) {
104104
runOnJS(handleOnVisibilityExited)();
105105
}
106106
}

src/components/useLazyContextValues.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useCallback, useMemo } from 'react';
1+
import React, { useCallback, useLayoutEffect, useMemo } from 'react';
22
import { StatusBar, type LayoutChangeEvent } from 'react-native';
33
import Animated, {
44
AnimatedRef,
@@ -42,6 +42,14 @@ export const useLazyContextValues = ({
4242
*/
4343
const scrollValue = useScrollViewOffset(ref);
4444

45+
const isScrollUnmounted = useSharedValue(false);
46+
useLayoutEffect(() => {
47+
return () => {
48+
isScrollUnmounted.value = true;
49+
};
50+
// eslint-disable-next-line react-hooks/exhaustive-deps -- shared values do not trigger re-renders
51+
}, []);
52+
4553
const containerStart = useDerivedValue(() =>
4654
_horizontal.value
4755
? _containerCoordinates.value.x
@@ -107,6 +115,7 @@ export const useLazyContextValues = ({
107115
startTrigger,
108116
endTrigger,
109117
horizontal: _horizontal,
118+
isScrollUnmounted,
110119
}),
111120
// eslint-disable-next-line react-hooks/exhaustive-deps -- shared values do not trigger re-renders
112121
[]

src/context/AnimatedContext.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ const initialContext = {
88
startTrigger: { value: 0 },
99
endTrigger: { value: 0 },
1010
horizontal: { value: false },
11+
isScrollUnmounted: { value: false },
1112
};
1213

1314
export const AnimatedContext = createContext(initialContext);

0 commit comments

Comments
 (0)