Skip to content

Commit b4b0fb5

Browse files
committed
refactor(lens): reuse reactive app state hook
1 parent 707bec8 commit b4b0fb5

4 files changed

Lines changed: 89 additions & 31 deletions

File tree

‎src/app/Scenes/Lens/Screens/LensCamera.tsx‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import { LensPermissionPlaceholder } from "app/Scenes/Lens/Components/LensPermis
1818
import { LensNavigationStack } from "app/Scenes/Lens/types"
1919
import { goBack } from "app/system/navigation/navigate"
2020
import { requestPhotos } from "app/utils/requestPhotos"
21-
import { useIsForeground } from "app/utils/useIsForeground"
21+
import useAppState from "app/utils/useAppState"
2222
import { useRef, useState } from "react"
2323
import { Linking } from "react-native"
2424

@@ -39,8 +39,8 @@ export const LensCamera: React.FC<Props> = ({ navigation }) => {
3939
const [containerSize, setContainerSize] = useState({ width: 0, height: 0 })
4040

4141
const isFocused = useIsFocused()
42-
const isForeground = useIsForeground()
43-
const isActive = isFocused && isForeground
42+
const { appState } = useAppState({})
43+
const isActive = isFocused && appState === "active"
4444

4545
const reportError = (context: string, error: unknown) => {
4646
if (__DEV__) {
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import { act, renderHook } from "@testing-library/react-native"
2+
import useAppState from "app/utils/useAppState"
3+
import { AppState, AppStateStatus } from "react-native"
4+
5+
const originalCurrentState = Object.getOwnPropertyDescriptor(AppState, "currentState")
6+
7+
describe("useAppState", () => {
8+
let handleStateChange: (state: AppStateStatus) => void
9+
const remove = jest.fn()
10+
11+
beforeEach(() => {
12+
jest.clearAllMocks()
13+
jest.spyOn(AppState, "addEventListener").mockImplementation((_type, handler) => {
14+
handleStateChange = handler
15+
return { remove }
16+
})
17+
})
18+
19+
afterEach(() => {
20+
jest.restoreAllMocks()
21+
if (originalCurrentState) {
22+
Object.defineProperty(AppState, "currentState", originalCurrentState)
23+
}
24+
})
25+
26+
it("returns the current app state and updates it when the app state changes", () => {
27+
Object.defineProperty(AppState, "currentState", {
28+
configurable: true,
29+
value: "background",
30+
})
31+
32+
const { result } = renderHook(() => useAppState({}))
33+
34+
expect(result.current.appState).toBe("background")
35+
36+
act(() => handleStateChange("active"))
37+
38+
expect(result.current.appState).toBe("active")
39+
})
40+
41+
it("preserves the foreground, background, and change callbacks", () => {
42+
Object.defineProperty(AppState, "currentState", {
43+
configurable: true,
44+
value: "active",
45+
})
46+
const onForeground = jest.fn()
47+
const onBackground = jest.fn()
48+
const onChange = jest.fn()
49+
50+
const { result } = renderHook(() => useAppState({ onForeground, onBackground, onChange }))
51+
52+
act(() => handleStateChange("inactive"))
53+
expect(result.current.appState).toBe("inactive")
54+
expect(onBackground).not.toHaveBeenCalled()
55+
56+
act(() => handleStateChange("active"))
57+
expect(onForeground).toHaveBeenCalledTimes(1)
58+
59+
act(() => handleStateChange("background"))
60+
expect(onBackground).toHaveBeenCalledTimes(1)
61+
expect(onChange).toHaveBeenNthCalledWith(1, "inactive")
62+
expect(onChange).toHaveBeenNthCalledWith(2, "active")
63+
expect(onChange).toHaveBeenNthCalledWith(3, "background")
64+
})
65+
66+
it("removes the app state listener on unmount", () => {
67+
const { unmount } = renderHook(() => useAppState({}))
68+
69+
unmount()
70+
71+
expect(remove).toHaveBeenCalledTimes(1)
72+
})
73+
})

‎src/app/utils/useAppState.tsx‎

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useEffect, useRef } from "react"
1+
import { useEffect, useRef, useState } from "react"
22
import { AppState, AppStateStatus } from "react-native"
33

44
export interface AppStateProps {
@@ -14,37 +14,43 @@ export default function useAppState({ onForeground, onBackground, onChange }: Ap
1414
* background - The app is running in the background. The user is either in another app or on the home screen
1515
* inactive [iOS] - This is a transition state that currently never happens for typical React Native apps.
1616
*/
17-
const appState = useRef(AppState.currentState)
17+
const previousAppState = useRef(AppState.currentState)
18+
const [appState, setAppState] = useState(AppState.currentState)
1819

1920
useEffect(() => {
2021
const _handleAppStateChange = (nextAppState: AppStateStatus) => {
2122
// App has come to the foreground
2223
if (
23-
appState.current.match(/inactive|background/) &&
24+
previousAppState.current.match(/inactive|background/) &&
2425
nextAppState === "active" &&
2526
onForeground
2627
) {
2728
onForeground()
2829
}
2930

3031
// App has come to the background
31-
if (appState.current.match(/active/) && nextAppState === "background" && onBackground) {
32+
if (
33+
previousAppState.current.match(/active/) &&
34+
nextAppState === "background" &&
35+
onBackground
36+
) {
3237
onBackground()
3338
}
3439

3540
if (onChange) {
3641
onChange(nextAppState)
3742
}
3843

39-
appState.current = nextAppState
44+
previousAppState.current = nextAppState
45+
setAppState(nextAppState)
4046
}
4147

4248
const sub = AppState.addEventListener("change", _handleAppStateChange)
4349

4450
return () => {
4551
sub.remove()
4652
}
47-
}, [appState.current, onForeground, onBackground])
53+
}, [onForeground, onBackground, onChange])
4854

49-
return { appState: appState.current }
55+
return { appState }
5056
}

‎src/app/utils/useIsForeground.ts‎

Lines changed: 0 additions & 21 deletions
This file was deleted.

0 commit comments

Comments
 (0)