Skip to content
This repository was archived by the owner on Feb 8, 2020. It is now read-only.

Commit 2b59f7e

Browse files
satya164osdnk
authored andcommitted
feat: add a useIsFocused hook to get focus state (#52)
1 parent 2b8f2ed commit 2b59f7e

File tree

9 files changed

+117
-9
lines changed

9 files changed

+117
-9
lines changed

README.md

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,17 @@ Screens cannot emit events as there is no `emit` method on a screen's `navigatio
228228

229229
If you don't need to get notified of focus change, but just need to check if the screen is currently focused in a callback, you can use the `navigation.isFocused()` method which returns a boolean. Note that it's not safe to use this in `render`. Only use it in callbacks, event listeners etc.
230230

231-
## Side-effects in focused screen
231+
## Additional utilities
232+
233+
### Access navigation anywhere
234+
235+
Passing the `navigation` prop down can be tedious. The library exports a `useNavigation` hook which can access the `navigation` prop from the parent screen:
236+
237+
```js
238+
const navigation = useNavigation();
239+
```
240+
241+
### Side-effects in focused screen
232242

233243
Sometimes we want to run side-effects when a screen is focused. A side effect may involve things like adding an event listener, fetching data, updating document title, etc. While this can be achieved using `focus` and `blur` events, it's not very ergonomic.
234244

@@ -257,12 +267,12 @@ The `useFocusEffect` is analogous to React's `useEffect` hook. The only differen
257267

258268
**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.
259269

260-
## Access navigation anywhere
270+
### Render based on focus state
261271

262-
Passing the `navigation` prop down can be tedious. The library exports a `useNavigation` hook which can access the `navigation` prop from the parent screen.
272+
We might want to render different content based on the current focus state of the screen. The library exports a `useIsFocused` hook to make this easier:
263273

264274
```js
265-
const navigation = useNavigation();
275+
const isFocused = useIsFocused();
266276
```
267277

268278
## Type-checking

packages/core/package.json

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,16 @@
44
"main": "src/index",
55
"license": "MIT",
66
"dependencies": {
7-
"shortid": "^2.2.14"
7+
"shortid": "^2.2.14",
8+
"use-subscription": "^1.0.0"
89
},
910
"devDependencies": {
1011
"@babel/core": "^7.4.5",
12+
"@types/react": "^16.8.19",
13+
"@types/shortid": "^0.0.29",
1114
"react": "^16.8.3",
1215
"react-native-testing-library": "^1.9.1",
13-
"react-test-renderer": "^16.8.3",
14-
"@types/react": "^16.8.19",
15-
"@types/shortid": "^0.0.29"
16+
"react-test-renderer": "^16.8.3"
1617
},
1718
"peerDependencies": {
1819
"react": "^16.8.3"
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import * as React from 'react';
2+
import { render, act } from 'react-native-testing-library';
3+
import useNavigationBuilder from '../useNavigationBuilder';
4+
import useIsFocused from '../useIsFocused';
5+
import NavigationContainer from '../NavigationContainer';
6+
import Screen from '../Screen';
7+
import MockRouter from './__fixtures__/MockRouter';
8+
9+
it('renders correct focus state', () => {
10+
const TestNavigator = (props: any): any => {
11+
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
12+
13+
return state.routes.map(route => descriptors[route.key].render());
14+
};
15+
16+
const Test = () => {
17+
const isFocused = useIsFocused();
18+
19+
return (
20+
<React.Fragment>{isFocused ? 'focused' : 'unfocused'}</React.Fragment>
21+
);
22+
};
23+
24+
const navigation = React.createRef<any>();
25+
26+
const root = render(
27+
<NavigationContainer ref={navigation}>
28+
<TestNavigator>
29+
<Screen name="first">{() => null}</Screen>
30+
<Screen name="second" component={Test} />
31+
<Screen name="third">{() => null}</Screen>
32+
</TestNavigator>
33+
</NavigationContainer>
34+
);
35+
36+
expect(root).toMatchInlineSnapshot(`"unfocused"`);
37+
38+
act(() => navigation.current.navigate('second'));
39+
40+
expect(root).toMatchInlineSnapshot(`"focused"`);
41+
42+
act(() => navigation.current.navigate('third'));
43+
44+
expect(root).toMatchInlineSnapshot(`"unfocused"`);
45+
46+
act(() => navigation.current.navigate('second'));
47+
48+
expect(root).toMatchInlineSnapshot(`"focused"`);
49+
});

packages/core/src/index.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,6 @@ export { default as createNavigator } from './createNavigator';
99
export { default as useNavigationBuilder } from './useNavigationBuilder';
1010
export { default as useNavigation } from './useNavigation';
1111
export { default as useFocusEffect } from './useFocusEffect';
12+
export { default as useIsFocused } from './useIsFocused';
1213

1314
export * from './types';

packages/core/src/types.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,7 @@ type NavigationHelpersCommon<
317317
* Check if the screen is focused. The method returns `true` if focused, `false` otherwise.
318318
* Note that this method doesn't re-render screen when the focus changes. So don't use it in `render`.
319319
* To get notified of focus changes, use `addListener('focus', cb)` and `addListener('blur', cb)`.
320+
* To conditionally render content based on focus state, use the `useIsFocused` hook.
320321
*/
321322
isFocused(): boolean;
322323

packages/core/src/useFocusEffect.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import useNavigation from './useNavigation';
44
type EffectCallback = (() => void) | (() => () => void);
55

66
/**
7-
* Hook to an effect in a focused screen, similar to `React.useEffect`.
7+
* Hook to run an effect in a focused screen, similar to `React.useEffect`.
88
* This can be used to perform side-effects such as fetching data or subscribing to events.
99
* The passed callback should be wrapped in `React.useCallback` to avoid running the effect too often.
1010
*

packages/core/src/useIsFocused.tsx

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import * as React from 'react';
2+
import { useSubscription } from 'use-subscription';
3+
import useNavigation from './useNavigation';
4+
5+
/**
6+
* Hook to get the current focus state of the screen. Returns a `true` if screen is focused, otherwise `false`.
7+
* This can be used if a component needs to render something based on the focus state.
8+
* It uses `use-subscription` under the hood for safer use in concurrent mode.
9+
*/
10+
export default function useIsFocused(): boolean {
11+
const navigation = useNavigation();
12+
const getCurrentValue = React.useCallback(navigation.isFocused, [navigation]);
13+
const subscribe = React.useCallback(
14+
(callback: (value: boolean) => void) => {
15+
const unsubscribeFocus = navigation.addListener('focus', () =>
16+
callback(true)
17+
);
18+
19+
const unsubscribeBlur = navigation.addListener('blur', () =>
20+
callback(false)
21+
);
22+
23+
return () => {
24+
unsubscribeFocus();
25+
unsubscribeBlur();
26+
};
27+
},
28+
[navigation]
29+
);
30+
31+
return useSubscription({
32+
getCurrentValue,
33+
subscribe,
34+
});
35+
}

typings/use-subscription.d.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
declare module 'use-subscription' {
2+
export function useSubscription<T>(options: {
3+
getCurrentValue: () => T;
4+
subscribe: (callback: (value: T) => void) => () => void;
5+
}): T;
6+
}

yarn.lock

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13961,6 +13961,11 @@ urlgrey@^0.4.4:
1396113961
resolved "https://registry.yarnpkg.com/urlgrey/-/urlgrey-0.4.4.tgz#892fe95960805e85519f1cd4389f2cb4cbb7652f"
1396213962
integrity sha1-iS/pWWCAXoVRnxzUOJ8stMu3ZS8=
1396313963

13964+
use-subscription@^1.0.0:
13965+
version "1.0.0"
13966+
resolved "https://registry.yarnpkg.com/use-subscription/-/use-subscription-1.0.0.tgz#25ed2161f75e9f6bd8c5c4acfe6087bfebfbfef4"
13967+
integrity sha512-PkDL9KSMN01nJYfa5c8O7Qbs7lmpzirfRWhWfIQN053hbIj5s1o5L7hrTzCfCCO2FsN2bKrU7ciRxxRcinmxAA==
13968+
1396413969
use@^3.1.0:
1396513970
version "3.1.1"
1396613971
resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f"

0 commit comments

Comments
 (0)