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

Commit

Permalink
feat: add a useIsFocused hook to get focus state (#52)
Browse files Browse the repository at this point in the history
  • Loading branch information
satya164 authored and osdnk committed Aug 13, 2019
1 parent 2b8f2ed commit 2b59f7e
Show file tree
Hide file tree
Showing 9 changed files with 117 additions and 9 deletions.
18 changes: 14 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,17 @@ Screens cannot emit events as there is no `emit` method on a screen's `navigatio

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.

## Side-effects in focused screen
## Additional utilities

### Access navigation anywhere

Passing the `navigation` prop down can be tedious. The library exports a `useNavigation` hook which can access the `navigation` prop from the parent screen:

```js
const navigation = useNavigation();
```

### Side-effects in focused screen

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.

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

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

## Access navigation anywhere
### Render based on focus state

Passing the `navigation` prop down can be tedious. The library exports a `useNavigation` hook which can access the `navigation` prop from the parent screen.
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:

```js
const navigation = useNavigation();
const isFocused = useIsFocused();
```

## Type-checking
Expand Down
9 changes: 5 additions & 4 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,16 @@
"main": "src/index",
"license": "MIT",
"dependencies": {
"shortid": "^2.2.14"
"shortid": "^2.2.14",
"use-subscription": "^1.0.0"
},
"devDependencies": {
"@babel/core": "^7.4.5",
"@types/react": "^16.8.19",
"@types/shortid": "^0.0.29",
"react": "^16.8.3",
"react-native-testing-library": "^1.9.1",
"react-test-renderer": "^16.8.3",
"@types/react": "^16.8.19",
"@types/shortid": "^0.0.29"
"react-test-renderer": "^16.8.3"
},
"peerDependencies": {
"react": "^16.8.3"
Expand Down
49 changes: 49 additions & 0 deletions packages/core/src/__tests__/useIsFocused.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import * as React from 'react';
import { render, act } from 'react-native-testing-library';
import useNavigationBuilder from '../useNavigationBuilder';
import useIsFocused from '../useIsFocused';
import NavigationContainer from '../NavigationContainer';
import Screen from '../Screen';
import MockRouter from './__fixtures__/MockRouter';

it('renders correct focus state', () => {
const TestNavigator = (props: any): any => {
const { state, descriptors } = useNavigationBuilder(MockRouter, props);

return state.routes.map(route => descriptors[route.key].render());
};

const Test = () => {
const isFocused = useIsFocused();

return (
<React.Fragment>{isFocused ? 'focused' : 'unfocused'}</React.Fragment>
);
};

const navigation = React.createRef<any>();

const root = render(
<NavigationContainer ref={navigation}>
<TestNavigator>
<Screen name="first">{() => null}</Screen>
<Screen name="second" component={Test} />
<Screen name="third">{() => null}</Screen>
</TestNavigator>
</NavigationContainer>
);

expect(root).toMatchInlineSnapshot(`"unfocused"`);

act(() => navigation.current.navigate('second'));

expect(root).toMatchInlineSnapshot(`"focused"`);

act(() => navigation.current.navigate('third'));

expect(root).toMatchInlineSnapshot(`"unfocused"`);

act(() => navigation.current.navigate('second'));

expect(root).toMatchInlineSnapshot(`"focused"`);
});
1 change: 1 addition & 0 deletions packages/core/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@ export { default as createNavigator } from './createNavigator';
export { default as useNavigationBuilder } from './useNavigationBuilder';
export { default as useNavigation } from './useNavigation';
export { default as useFocusEffect } from './useFocusEffect';
export { default as useIsFocused } from './useIsFocused';

export * from './types';
1 change: 1 addition & 0 deletions packages/core/src/types.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,7 @@ type NavigationHelpersCommon<
* Check if the screen is focused. The method returns `true` if focused, `false` otherwise.
* Note that this method doesn't re-render screen when the focus changes. So don't use it in `render`.
* To get notified of focus changes, use `addListener('focus', cb)` and `addListener('blur', cb)`.
* To conditionally render content based on focus state, use the `useIsFocused` hook.
*/
isFocused(): boolean;

Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/useFocusEffect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import useNavigation from './useNavigation';
type EffectCallback = (() => void) | (() => () => void);

/**
* Hook to an effect in a focused screen, similar to `React.useEffect`.
* Hook to run an effect in a focused screen, similar to `React.useEffect`.
* This can be used to perform side-effects such as fetching data or subscribing to events.
* The passed callback should be wrapped in `React.useCallback` to avoid running the effect too often.
*
Expand Down
35 changes: 35 additions & 0 deletions packages/core/src/useIsFocused.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import * as React from 'react';
import { useSubscription } from 'use-subscription';
import useNavigation from './useNavigation';

/**
* Hook to get the current focus state of the screen. Returns a `true` if screen is focused, otherwise `false`.
* This can be used if a component needs to render something based on the focus state.
* It uses `use-subscription` under the hood for safer use in concurrent mode.
*/
export default function useIsFocused(): boolean {
const navigation = useNavigation();
const getCurrentValue = React.useCallback(navigation.isFocused, [navigation]);
const subscribe = React.useCallback(
(callback: (value: boolean) => void) => {
const unsubscribeFocus = navigation.addListener('focus', () =>
callback(true)
);

const unsubscribeBlur = navigation.addListener('blur', () =>
callback(false)
);

return () => {
unsubscribeFocus();
unsubscribeBlur();
};
},
[navigation]
);

return useSubscription({
getCurrentValue,
subscribe,
});
}
6 changes: 6 additions & 0 deletions typings/use-subscription.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
declare module 'use-subscription' {
export function useSubscription<T>(options: {
getCurrentValue: () => T;
subscribe: (callback: (value: T) => void) => () => void;
}): T;
}
5 changes: 5 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -13961,6 +13961,11 @@ urlgrey@^0.4.4:
resolved "https://registry.yarnpkg.com/urlgrey/-/urlgrey-0.4.4.tgz#892fe95960805e85519f1cd4389f2cb4cbb7652f"
integrity sha1-iS/pWWCAXoVRnxzUOJ8stMu3ZS8=

use-subscription@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/use-subscription/-/use-subscription-1.0.0.tgz#25ed2161f75e9f6bd8c5c4acfe6087bfebfbfef4"
integrity sha512-PkDL9KSMN01nJYfa5c8O7Qbs7lmpzirfRWhWfIQN053hbIj5s1o5L7hrTzCfCCO2FsN2bKrU7ciRxxRcinmxAA==

use@^3.1.0:
version "3.1.1"
resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f"
Expand Down

0 comments on commit 2b59f7e

Please sign in to comment.