Skip to content

Commit c5a7230

Browse files
committed
refactor: address coding standards - useSyncExternalStore, shared illustration hook, reduce duplication
1 parent 19f9e4b commit c5a7230

8 files changed

Lines changed: 60 additions & 129 deletions

File tree

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import type {StyleProp, ViewStyle} from 'react-native';
2+
import type {IllustrationName} from '@components/Icon/IllustrationLoader';
3+
import type DotLottieAnimation from '@components/LottieAnimations/types';
4+
import type {SectionProps} from '@components/Section';
5+
import Accessibility from '@libs/Accessibility';
6+
import {useMemoizedLazyIllustrations} from './useLazyAsset';
7+
8+
/** Returns a static SVG when reduced motion is enabled, or a Lottie animation otherwise. */
9+
function useSectionIllustrationWithMotion(
10+
animationSource: DotLottieAnimation,
11+
illustrationKey: IllustrationName,
12+
staticIllustrationStyle?: StyleProp<ViewStyle>,
13+
animationIllustrationStyle?: StyleProp<ViewStyle>,
14+
): Pick<SectionProps, 'illustration' | 'illustrationStyle'> {
15+
const isReduceMotionEnabled = Accessibility.useReducedMotion();
16+
const illustrations = useMemoizedLazyIllustrations([illustrationKey] as const);
17+
18+
if (isReduceMotionEnabled) {
19+
return {
20+
illustration: illustrations[illustrationKey],
21+
illustrationStyle: staticIllustrationStyle,
22+
};
23+
}
24+
25+
return {
26+
illustration: animationSource,
27+
illustrationStyle: animationIllustrationStyle,
28+
};
29+
}
30+
31+
export default useSectionIllustrationWithMotion;

src/libs/Accessibility/index.ts

Lines changed: 17 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {useCallback, useEffect, useState} from 'react';
1+
import {useCallback, useEffect, useState, useSyncExternalStore} from 'react';
22
import type {LayoutChangeEvent} from 'react-native';
33
import {AccessibilityInfo} from 'react-native';
44
import moveAccessibilityFocus from './moveAccessibilityFocus';
@@ -18,50 +18,29 @@ const useScreenReaderStatus = (): boolean => {
1818
return isScreenReaderEnabled;
1919
};
2020

21-
/**
22-
* Hook that returns whether the user has enabled the "reduce motion" accessibility setting.
23-
* This is used to disable animations for users who are sensitive to motion.
24-
* Works on iOS, Android, and Web (via react-native-web which uses prefers-reduced-motion media query).
25-
*/
26-
// Module-level cache so new hook instances start with the last known value
27-
// instead of defaulting to false (which causes a race condition on remount)
2821
let cachedReduceMotionValue = false;
2922

30-
const useReducedMotion = (): boolean => {
31-
const [isReduceMotionEnabled, setIsReduceMotionEnabled] = useState(cachedReduceMotionValue);
23+
function subscribeReduceMotion(callback: () => void) {
24+
const subscription = AccessibilityInfo.addEventListener('reduceMotionChanged', (enabled) => {
25+
cachedReduceMotionValue = enabled;
26+
callback();
27+
});
3228

33-
useEffect(() => {
34-
let isMounted = true;
35-
36-
const subscription = AccessibilityInfo.addEventListener('reduceMotionChanged', (enabled) => {
29+
AccessibilityInfo.isReduceMotionEnabled()
30+
.then((enabled) => {
3731
cachedReduceMotionValue = enabled;
38-
setIsReduceMotionEnabled(enabled);
39-
});
32+
callback();
33+
})
34+
.catch(() => {});
4035

41-
AccessibilityInfo.isReduceMotionEnabled()
42-
.then((enabled) => {
43-
cachedReduceMotionValue = enabled;
44-
if (!isMounted) {
45-
return;
46-
}
47-
setIsReduceMotionEnabled(enabled);
48-
})
49-
.catch(() => {
50-
// If the check fails, default to false (animations enabled)
51-
if (!isMounted) {
52-
return;
53-
}
54-
setIsReduceMotionEnabled(false);
55-
});
36+
return () => subscription?.remove();
37+
}
5638

57-
return () => {
58-
isMounted = false;
59-
subscription?.remove();
60-
};
61-
}, []);
39+
function getReduceMotionSnapshot() {
40+
return cachedReduceMotionValue;
41+
}
6242

63-
return isReduceMotionEnabled;
64-
};
43+
const useReducedMotion = (): boolean => useSyncExternalStore(subscribeReduceMotion, getReduceMotionSnapshot, () => false);
6544

6645
const getHitSlopForSize = ({x, y}: HitSlop) => {
6746
/* according to https://developer.apple.com/design/human-interface-guidelines/ios/visual-design/adaptivity-and-layout/
Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,11 @@
11
import LottieAnimations from '@components/LottieAnimations';
2-
import {useMemoizedLazyIllustrations} from '@hooks/useLazyAsset';
2+
import useSectionIllustrationWithMotion from '@hooks/useSectionIllustrationWithMotion';
33
import useThemeStyles from '@hooks/useThemeStyles';
4-
import Accessibility from '@libs/Accessibility';
54
import type UseSaveTheWorldSectionIllustration from './types';
65

76
const useSaveTheWorldSectionIllustration: UseSaveTheWorldSectionIllustration = () => {
8-
const isReduceMotionEnabled = Accessibility.useReducedMotion();
9-
const illustrations = useMemoizedLazyIllustrations(['SaveTheWorldScale']);
107
const styles = useThemeStyles();
11-
12-
if (isReduceMotionEnabled) {
13-
return {
14-
illustration: illustrations.SaveTheWorldScale,
15-
illustrationStyle: styles.saveTheWorldStaticIllustration,
16-
};
17-
}
18-
19-
return {
20-
illustration: LottieAnimations.SaveTheWorld,
21-
};
8+
return useSectionIllustrationWithMotion(LottieAnimations.SaveTheWorld, 'SaveTheWorldScale', styles.saveTheWorldStaticIllustration);
229
};
2310

2411
export default useSaveTheWorldSectionIllustration;
Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,11 @@
11
import LottieAnimations from '@components/LottieAnimations';
2-
import {useMemoizedLazyIllustrations} from '@hooks/useLazyAsset';
2+
import useSectionIllustrationWithMotion from '@hooks/useSectionIllustrationWithMotion';
33
import useThemeStyles from '@hooks/useThemeStyles';
4-
import Accessibility from '@libs/Accessibility';
54
import type UseAboutSectionIllustration from './types';
65

76
const useAboutSectionIllustration: UseAboutSectionIllustration = () => {
8-
const isReduceMotionEnabled = Accessibility.useReducedMotion();
9-
const illustrations = useMemoizedLazyIllustrations(['TiltedCoinExpensify']);
107
const styles = useThemeStyles();
11-
12-
if (isReduceMotionEnabled) {
13-
return {
14-
illustration: illustrations.TiltedCoinExpensify,
15-
illustrationStyle: styles.aboutStaticIllustration,
16-
};
17-
}
18-
19-
return {
20-
illustration: LottieAnimations.Coin,
21-
};
8+
return useSectionIllustrationWithMotion(LottieAnimations.Coin, 'TiltedCoinExpensify', styles.aboutStaticIllustration);
229
};
2310

2411
export default useAboutSectionIllustration;
Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,11 @@
11
import LottieAnimations from '@components/LottieAnimations';
2-
import {useMemoizedLazyIllustrations} from '@hooks/useLazyAsset';
2+
import useSectionIllustrationWithMotion from '@hooks/useSectionIllustrationWithMotion';
33
import useThemeStyles from '@hooks/useThemeStyles';
4-
import Accessibility from '@libs/Accessibility';
54
import type UsePreferencesSectionIllustration from './types';
65

76
const usePreferencesSectionIllustration: UsePreferencesSectionIllustration = () => {
8-
const isReduceMotionEnabled = Accessibility.useReducedMotion();
9-
const illustrations = useMemoizedLazyIllustrations(['DjBoothReferenceHands']);
107
const styles = useThemeStyles();
11-
12-
if (isReduceMotionEnabled) {
13-
return {
14-
illustration: illustrations.DjBoothReferenceHands,
15-
illustrationStyle: styles.preferencesStaticIllustration,
16-
};
17-
}
18-
19-
return {
20-
illustration: LottieAnimations.PreferencesDJ,
21-
};
8+
return useSectionIllustrationWithMotion(LottieAnimations.PreferencesDJ, 'DjBoothReferenceHands', styles.preferencesStaticIllustration);
229
};
2310

2411
export default usePreferencesSectionIllustration;
Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,11 @@
11
import LottieAnimations from '@components/LottieAnimations';
2-
import {useMemoizedLazyIllustrations} from '@hooks/useLazyAsset';
2+
import useSectionIllustrationWithMotion from '@hooks/useSectionIllustrationWithMotion';
33
import useThemeStyles from '@hooks/useThemeStyles';
4-
import Accessibility from '@libs/Accessibility';
54
import type UseSecuritySettingsSectionIllustration from './types';
65

76
const useSecuritySettingsSectionIllustration: UseSecuritySettingsSectionIllustration = () => {
8-
const isReduceMotionEnabled = Accessibility.useReducedMotion();
9-
const illustrations = useMemoizedLazyIllustrations(['Safe']);
107
const styles = useThemeStyles();
11-
12-
if (isReduceMotionEnabled) {
13-
return {
14-
illustration: illustrations.Safe,
15-
illustrationStyle: styles.securitySettingsStaticIllustration,
16-
};
17-
}
18-
19-
return {
20-
illustration: LottieAnimations.Safe,
21-
};
8+
return useSectionIllustrationWithMotion(LottieAnimations.Safe, 'Safe', styles.securitySettingsStaticIllustration);
229
};
2310

2411
export default useSecuritySettingsSectionIllustration;
Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,11 @@
11
import LottieAnimations from '@components/LottieAnimations';
2-
import {useMemoizedLazyIllustrations} from '@hooks/useLazyAsset';
2+
import useSectionIllustrationWithMotion from '@hooks/useSectionIllustrationWithMotion';
33
import useThemeStyles from '@hooks/useThemeStyles';
4-
import Accessibility from '@libs/Accessibility';
54
import type UseTroubleshootSectionIllustration from './types';
65

76
const useTroubleshootSectionIllustration: UseTroubleshootSectionIllustration = () => {
8-
const isReduceMotionEnabled = Accessibility.useReducedMotion();
9-
const illustrations = useMemoizedLazyIllustrations(['WorkspaceScene']);
107
const styles = useThemeStyles();
11-
12-
if (isReduceMotionEnabled) {
13-
return {
14-
illustration: illustrations.WorkspaceScene,
15-
illustrationStyle: styles.troubleshootStaticIllustration,
16-
};
17-
}
18-
19-
return {
20-
illustration: LottieAnimations.Desk,
21-
};
8+
return useSectionIllustrationWithMotion(LottieAnimations.Desk, 'WorkspaceScene', styles.troubleshootStaticIllustration);
229
};
2310

2411
export default useTroubleshootSectionIllustration;
Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,11 @@
11
import LottieAnimations from '@components/LottieAnimations';
2-
import {useMemoizedLazyIllustrations} from '@hooks/useLazyAsset';
2+
import useSectionIllustrationWithMotion from '@hooks/useSectionIllustrationWithMotion';
33
import useThemeStyles from '@hooks/useThemeStyles';
4-
import Accessibility from '@libs/Accessibility';
54
import type UseWalletSectionIllustration from './types';
65

76
const useWalletSectionIllustration: UseWalletSectionIllustration = () => {
8-
const isReduceMotionEnabled = Accessibility.useReducedMotion();
9-
const illustrations = useMemoizedLazyIllustrations(['BigVault']);
107
const styles = useThemeStyles();
11-
12-
if (isReduceMotionEnabled) {
13-
return {
14-
illustration: illustrations.BigVault,
15-
illustrationStyle: styles.walletStaticIllustration,
16-
};
17-
}
18-
19-
return {
20-
illustration: LottieAnimations.BankVault,
21-
illustrationStyle: styles.walletLottieIllustration,
22-
};
8+
return useSectionIllustrationWithMotion(LottieAnimations.BankVault, 'BigVault', styles.walletStaticIllustration, styles.walletLottieIllustration);
239
};
2410

2511
export default useWalletSectionIllustration;

0 commit comments

Comments
 (0)