Skip to content
This repository was archived by the owner on Dec 3, 2022. It is now read-only.

Commit 2a5b198

Browse files
authored
Merge pull request #43 from react-navigation/feature/focus-based-hooks
useFocusEffect / useIsFocused
2 parents 11aa5b1 + d354567 commit 2a5b198

File tree

2 files changed

+139
-3
lines changed

2 files changed

+139
-3
lines changed

README.md

Lines changed: 74 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
# React Navigation Hooks
2+
23
[![npm version](https://badge.fury.io/js/react-navigation-hooks.svg)](https://badge.fury.io/js/react-navigation-hooks) [![npm downloads](https://img.shields.io/npm/dm/react-navigation-hooks.svg)](https://www.npmjs.com/package/react-navigation-hooks) [![CircleCI badge](https://circleci.com/gh/react-navigation/hooks/tree/master.svg?style=shield)](https://circleci.com/gh/react-navigation/hooks/tree/master) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://reactnavigation.org/docs/contributing.html)
34

4-
🏄‍♀️ Surfing the wave of React Hook hype with a few convenience hooks for `@react-navigation/core` v3. Destined to work on web, server, and React Native. Contributions welcome!
5+
🏄‍♀️ Surfing the wave of React Hook hype with a few convenience hooks for `@react-navigation/core` v3/v4. Destined to work on web, server, and React Native. Contributions welcome!
6+
7+
**IMPORTANT**: [react-navigation v5](https://github.com/react-navigation/navigation-ex) is already on its way and is a full rewrite (including hooks). This project will not live past v4, and will try to make the migration path from v4 to v5 easy by not introducing any new hook that won't be in v5.
58

69
## Examples (web only so far)
710

@@ -95,13 +98,80 @@ function ReportNavigationEvents() {
9598

9699
The event payload will be the same as provided by `addListener`, as documented here: https://reactnavigation.org/docs/en/navigation-prop.html#addlistener-subscribe-to-updates-to-navigation-lifecycle
97100

101+
### useIsFocused()
102+
103+
Convenient way to know if the screen currently has focus.
104+
105+
```js
106+
function MyScreen() {
107+
const isFocused = useIsFocused();
108+
return <Text>{isFocused ? 'Focused' : 'Not Focused'}</Text>;
109+
}
110+
```
111+
112+
### useFocusEffect(callback)
113+
114+
Permit to execute an effect when the screen takes focus, and cleanup the effect when the screen loses focus.
115+
116+
```js
117+
function MyScreen() {
118+
useFocusEffect(useCallback(() => {
119+
console.debug("screen takes focus");
120+
return () => console.debug("screen loses focus");
121+
}, []));
122+
return <View>...</View>;
123+
}
124+
```
125+
126+
**NOTE**: To avoid the running the effect too often, it's important to wrap the callback in useCallback before passing it to `useFocusEffect` as shown in the example. The effect will re-execute everytime the callback changes if the screen is focused.
127+
128+
`useFocusEffect` can be helpful to refetch some screen data on params changes:
129+
130+
```js
131+
function Profile({ userId }) {
132+
const [user, setUser] = React.useState(null);
133+
134+
const fetchUser = React.useCallback(() => {
135+
const request = API.fetchUser(userId).then(
136+
data => setUser(data),
137+
error => alert(error.message)
138+
);
139+
140+
return () => request.abort();
141+
}, [userId]);
142+
143+
useFocusEffect(fetchUser);
144+
145+
return <ProfileContent user={user} />;
146+
}
147+
```
148+
149+
150+
`useFocusEffect` can be helpful to handle hardware back behavior on currently focused screen:
151+
152+
```js
153+
const useBackHandler = (backHandler: () => boolean) => {
154+
useFocusEffect(() => {
155+
const subscription = BackHandler.addEventListener('hardwareBackPress', backHandler);
156+
return () => subscription.remove();
157+
});
158+
};
159+
```
160+
161+
162+
163+
98164
### useFocusState()
99165

166+
**deprecated**: this hook does not exist in v5, you should rather use `useIsFocused`
167+
168+
100169
Convenient way of subscribing to events and observing focus state of the current screen.
101170

102171
```js
103-
function MyFocusTag() {
104-
return <p>{useFocusState().isFocused ? 'Focused' : 'Not Focused'}</p>;
172+
function MyScreen() {
173+
const focusState = useFocusState();
174+
return <Text>{focusState.isFocused ? 'Focused' : 'Not Focused'}</Text>;
105175
}
106176
```
107177

@@ -111,3 +181,4 @@ One (always, and only one) of the following values will be true in the focus sta
111181
- isBlurring
112182
- isBlurred
113183
- isFocusing
184+

src/Hooks.ts

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {
22
useState,
33
useContext,
4+
useEffect,
45
useLayoutEffect,
56
useRef,
67
useCallback,
@@ -143,3 +144,67 @@ export function useFocusState() {
143144

144145
return focusState;
145146
}
147+
148+
type EffectCallback = (() => void) | (() => () => void);
149+
150+
// Inspired by same hook from react-navigation v5
151+
// See https://github.com/react-navigation/hooks/issues/39#issuecomment-534694135
152+
export const useFocusEffect = (callback: EffectCallback) => {
153+
const navigation = useNavigation();
154+
155+
useEffect(() => {
156+
let isFocused = false;
157+
let cleanup: (() => void) | void;
158+
159+
if (navigation.isFocused()) {
160+
cleanup = callback();
161+
isFocused = true;
162+
}
163+
164+
const focusSubscription = navigation.addListener('willFocus', () => {
165+
// If callback was already called for focus, avoid calling it again
166+
// The focus event may also fire on intial render, so we guard against runing the effect twice
167+
if (isFocused) {
168+
return;
169+
}
170+
171+
cleanup && cleanup();
172+
cleanup = callback();
173+
isFocused = true;
174+
});
175+
176+
const blurSubscription = navigation.addListener('willBlur', () => {
177+
cleanup && cleanup();
178+
cleanup = undefined;
179+
isFocused = false;
180+
});
181+
182+
return () => {
183+
cleanup && cleanup();
184+
focusSubscription.remove();
185+
blurSubscription.remove();
186+
};
187+
}, [callback, navigation]);
188+
};
189+
190+
export const useIsFocused = () => {
191+
const navigation = useNavigation();
192+
const getNavigation = useGetter(navigation);
193+
const [focused, setFocused] = useState(navigation.isFocused);
194+
195+
useEffect(() => {
196+
const nav = getNavigation();
197+
const focusSubscription = nav.addListener('willFocus', () =>
198+
setFocused(true)
199+
);
200+
const blurSubscription = nav.addListener('willBlur', () =>
201+
setFocused(false)
202+
);
203+
return () => {
204+
focusSubscription.remove();
205+
blurSubscription.remove();
206+
};
207+
}, [getNavigation]);
208+
209+
return focused;
210+
};

0 commit comments

Comments
 (0)