Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Propose feature: add useLinking custom hook #404

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ yarn add @react-native-community/hooks
- [useDeviceOrientation](https://github.com/react-native-community/hooks#usedeviceorientation)
- [useLayout](https://github.com/react-native-community/hooks#uselayout)
- [useRefresh](https://github.com/react-native-community/hooks#useRefresh)
- [useLinking](https://github.com/react-native-community/hooks#useLinking)

### `useAccessibilityInfo`

Expand Down Expand Up @@ -153,6 +154,18 @@ const { isRefreshing, onRefresh } = useRefresh(fetch);
/>
```

### `useLinking`

useLinking can handle incoming your app's deeplinks and opening external urls through the [`Linking`](https://reactnative.dev/docs/linking) API.

```js
import {useLinking} from '@react-native-community/hooks'

const { deepLink, openLinkInBrowser } = useLinking()

console.log('Initial deep link is:', deepLink)
```

[version-badge]: https://img.shields.io/npm/v/@react-native-community/hooks.svg?style=flat-square
[package]: https://www.npmjs.com/package/@react-native-community/hooks

Expand Down
58 changes: 58 additions & 0 deletions src/useLinking.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import {Linking} from 'react-native'
import {act, renderHook} from '@testing-library/react-hooks'

import {useLinking} from './useLinking'

jest.mock('react-native', () => ({
Linking: {
addEventListener: jest.fn((_, fn) => ({remove: jest.fn()})),
getInitialURL: jest.fn(() => Promise.resolve()),
openSettings: jest.fn(() => Promise.resolve()),
canOpenURL: jest.fn(() => Promise.resolve(true)),
openURL: jest.fn((url: string) => Promise.resolve()),
},
}))

describe('useLinking', () => {
it('should return deeplink as null', () => {
const {result, waitForNextUpdate} = renderHook(() => useLinking())

waitForNextUpdate()

expect(result.current.deepLink).toBe(null)
})

it('calls getInitialURL with initial deeplink url', async () => {
const url = 'app://magic_screen'
const getInitialURLSpy = jest.spyOn(Linking, 'getInitialURL')
getInitialURLSpy.mockResolvedValueOnce(url)

const {result, waitForNextUpdate} = renderHook(() => useLinking())

await waitForNextUpdate()

expect(result.current.deepLink).toBe('app://magic_screen')
})

it('should open link in browser', async () => {
const {result} = renderHook(() => useLinking())
const url = 'https://reactnative.dev'

await act(async () => {
result.current.openLinkInBrowser(url)
})

expect(Linking.canOpenURL).toHaveBeenCalledWith(url)
expect(Linking.openURL).toHaveBeenCalledWith(url)
})

it('should open app settings', async () => {
const {result} = renderHook(() => useLinking())

await act(async () => {
result.current.openAppSettings()
})

expect(Linking.openSettings).toHaveBeenCalled()
})
})
30 changes: 30 additions & 0 deletions src/useLinking.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import {useEffect, useState} from 'react'
import {Linking} from 'react-native'

const useLinking = () => {
const [deepLink, setDeepLink] = useState<string | null>(null)

const openLinkInBrowser = (url: string) => {
Linking.canOpenURL(url).then((canOpen) => canOpen && Linking.openURL(url))
}

const openAppSettings = async () => await Linking.openSettings()

const handleURLChange = (event: {url: string}) => {
setDeepLink(event.url)
}

useEffect(() => {
Linking.getInitialURL().then((url) => setDeepLink(url))
}, [])

useEffect(() => {
const listener = Linking.addEventListener('url', handleURLChange)

return () => listener.remove()
}, [])

return {openLinkInBrowser, openAppSettings, deepLink}
}

export {useLinking}