UNPKG

3.86 kBPlain TextView Raw
1// A fork of `useFocusEffect` that waits for the navigation state to load before
2// running the effect. This is especially useful for native redirects.
3import * as React from 'react'
4
5import { useOptionalNavigation } from './link/useLoadedNavigation'
6
7type EffectCallback = () => undefined | void | (() => void)
8
9/**
10 * Hook to run an effect in a focused screen, similar to `React.useEffect`.
11 * This can be used to perform side-effects such as fetching data or subscribing to events.
12 * The passed callback should be wrapped in `React.useCallback` to avoid running the effect too often.
13 *
14 * @param callback Memoized callback containing the effect, should optionally return a cleanup function.
15 */
16export function useFocusEffect(effect: EffectCallback, do_not_pass_a_second_prop?: never) {
17 const navigation = useOptionalNavigation()
18
19 if (do_not_pass_a_second_prop !== undefined) {
20 const message =
21 "You passed a second argument to 'useFocusEffect', but it only accepts one argument. " +
22 "If you want to pass a dependency array, you can use 'React.useCallback':\n\n" +
23 'useFocusEffect(\n' +
24 ' React.useCallback(() => {\n' +
25 ' // Your code here\n' +
26 ' }, [depA, depB])\n' +
27 ');\n\n' +
28 'See usage guide: https://reactnavigation.org/docs/use-focus-effect'
29
30 console.error(message)
31 }
32
33 React.useEffect(() => {
34 if (!navigation) {
35 return
36 }
37
38 let isFocused = false
39 let cleanup: undefined | void | (() => void)
40
41 const callback = () => {
42 const destroy = effect()
43
44 if (destroy === undefined || typeof destroy === 'function') {
45 return destroy
46 }
47
48 if (process.env.NODE_ENV !== 'production') {
49 let message =
50 'An effect function must not return anything besides a function, which is used for clean-up.'
51
52 if (destroy === null) {
53 message +=
54 " You returned 'null'. If your effect does not require clean-up, return 'undefined' (or nothing)."
55 } else if (typeof (destroy as any).then === 'function') {
56 message +=
57 "\n\nIt looks like you wrote 'useFocusEffect(async () => ...)' or returned a Promise. " +
58 'Instead, write the async function inside your effect ' +
59 'and call it immediately:\n\n' +
60 'useFocusEffect(\n' +
61 ' React.useCallback(() => {\n' +
62 ' async function fetchData() {\n' +
63 ' // You can await here\n' +
64 ' const response = await MyAPI.getData(someId);\n' +
65 ' // ...\n' +
66 ' }\n\n' +
67 ' fetchData();\n' +
68 ' }, [someId])\n' +
69 ');\n\n' +
70 'See usage guide: https://reactnavigation.org/docs/use-focus-effect'
71 } else {
72 message += ` You returned '${JSON.stringify(destroy)}'.`
73 }
74
75 console.error(message)
76 }
77 }
78
79 // We need to run the effect on intial render/dep changes if the screen is focused
80 if (navigation.isFocused()) {
81 cleanup = callback()
82 isFocused = true
83 }
84
85 const unsubscribeFocus = navigation.addListener('focus', () => {
86 // If callback was already called for focus, avoid calling it again
87 // The focus event may also fire on intial render, so we guard against runing the effect twice
88 if (isFocused) {
89 return
90 }
91
92 if (cleanup !== undefined) {
93 cleanup()
94 }
95
96 cleanup = callback()
97 isFocused = true
98 })
99
100 const unsubscribeBlur = navigation.addListener('blur', () => {
101 if (cleanup !== undefined) {
102 cleanup()
103 }
104
105 cleanup = undefined
106 isFocused = false
107 })
108
109 return () => {
110 if (cleanup !== undefined) {
111 cleanup()
112 }
113
114 unsubscribeFocus()
115 unsubscribeBlur()
116 }
117 }, [effect, navigation])
118}
119
\No newline at end of file