Skip to content

Commit e58ece8

Browse files
committed
feat: add useLinking custom hook
1 parent 1b19bc0 commit e58ece8

File tree

3 files changed

+101
-0
lines changed

3 files changed

+101
-0
lines changed

Diff for: README.md

+13
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ yarn add @react-native-community/hooks
3131
- [useDeviceOrientation](https://github.com/react-native-community/hooks#usedeviceorientation)
3232
- [useLayout](https://github.com/react-native-community/hooks#uselayout)
3333
- [useRefresh](https://github.com/react-native-community/hooks#useRefresh)
34+
- [useLinking](https://github.com/react-native-community/hooks#useLinking)
3435

3536
### `useAccessibilityInfo`
3637

@@ -153,6 +154,18 @@ const { isRefreshing, onRefresh } = useRefresh(fetch);
153154
/>
154155
```
155156

157+
### `useLinking`
158+
159+
useLinking can handle incoming your app's deeplinks and opening external urls through the [`Linking`](https://reactnative.dev/docs/linking) API.
160+
161+
```js
162+
import {useLinking} from '@react-native-community/hooks'
163+
164+
const { deepLink, openLinkInBrowser } = useLinking()
165+
166+
console.log('Initial deep link is:', deepLink)
167+
```
168+
156169
[version-badge]: https://img.shields.io/npm/v/@react-native-community/hooks.svg?style=flat-square
157170
[package]: https://www.npmjs.com/package/@react-native-community/hooks
158171

Diff for: src/useLinking.test.ts

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import {Linking} from 'react-native'
2+
import {act, renderHook} from '@testing-library/react-hooks'
3+
4+
import {useLinking} from './useLinking'
5+
6+
jest.mock('react-native', () => ({
7+
Linking: {
8+
addEventListener: jest.fn((_, fn) => ({remove: jest.fn()})),
9+
getInitialURL: jest.fn(() => Promise.resolve()),
10+
openSettings: jest.fn(() => Promise.resolve()),
11+
canOpenURL: jest.fn(() => Promise.resolve(true)),
12+
openURL: jest.fn((url: string) => Promise.resolve()),
13+
},
14+
}))
15+
16+
describe('useLinking', () => {
17+
it('should return deeplink as null', () => {
18+
const {result, waitForNextUpdate} = renderHook(() => useLinking())
19+
20+
waitForNextUpdate()
21+
22+
expect(result.current.deepLink).toBe(null)
23+
})
24+
25+
it('calls getInitialURL with initial deeplink url', async () => {
26+
const url = 'app://magic_screen'
27+
const getInitialURLSpy = jest.spyOn(Linking, 'getInitialURL')
28+
getInitialURLSpy.mockResolvedValueOnce(url)
29+
30+
const {result, waitForNextUpdate} = renderHook(() => useLinking())
31+
32+
await waitForNextUpdate()
33+
34+
expect(result.current.deepLink).toBe('app://magic_screen')
35+
})
36+
37+
it('should open link in browser', async () => {
38+
const {result} = renderHook(() => useLinking())
39+
const url = 'https://reactnative.dev'
40+
41+
await act(async () => {
42+
result.current.openLinkInBrowser(url)
43+
})
44+
45+
expect(Linking.canOpenURL).toHaveBeenCalledWith(url)
46+
expect(Linking.openURL).toHaveBeenCalledWith(url)
47+
})
48+
49+
it('should open app settings', async () => {
50+
const {result} = renderHook(() => useLinking())
51+
52+
await act(async () => {
53+
result.current.openAppSettings()
54+
})
55+
56+
expect(Linking.openSettings).toHaveBeenCalled()
57+
})
58+
})

Diff for: src/useLinking.ts

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import {useEffect, useState} from 'react'
2+
import {Linking} from 'react-native'
3+
4+
const useLinking = () => {
5+
const [deepLink, setDeepLink] = useState<string | null>(null)
6+
7+
const openLinkInBrowser = (url: string) => {
8+
Linking.canOpenURL(url).then((canOpen) => canOpen && Linking.openURL(url))
9+
}
10+
11+
const openAppSettings = async () => await Linking.openSettings()
12+
13+
const handleURLChange = (event: {url: string}) => {
14+
setDeepLink(event.url)
15+
}
16+
17+
useEffect(() => {
18+
Linking.getInitialURL().then((url) => setDeepLink(url))
19+
}, [])
20+
21+
useEffect(() => {
22+
const listener = Linking.addEventListener('url', handleURLChange)
23+
24+
return () => listener.remove()
25+
}, [])
26+
27+
return {openLinkInBrowser, openAppSettings, deepLink}
28+
}
29+
30+
export {useLinking}

0 commit comments

Comments
 (0)