From e58ece82ae234bb595893d2afed1333b4bc7da35 Mon Sep 17 00:00:00 2001 From: "Jeferson S. Brito" Date: Mon, 18 Sep 2023 21:35:07 -0300 Subject: [PATCH 1/2] feat: add useLinking custom hook --- README.md | 13 ++++++++++ src/useLinking.test.ts | 58 ++++++++++++++++++++++++++++++++++++++++++ src/useLinking.ts | 30 ++++++++++++++++++++++ 3 files changed, 101 insertions(+) create mode 100644 src/useLinking.test.ts create mode 100644 src/useLinking.ts diff --git a/README.md b/README.md index 5edcb738..090abb54 100644 --- a/README.md +++ b/README.md @@ -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` @@ -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 diff --git a/src/useLinking.test.ts b/src/useLinking.test.ts new file mode 100644 index 00000000..e30c71d2 --- /dev/null +++ b/src/useLinking.test.ts @@ -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() + }) +}) diff --git a/src/useLinking.ts b/src/useLinking.ts new file mode 100644 index 00000000..9bb5b1a1 --- /dev/null +++ b/src/useLinking.ts @@ -0,0 +1,30 @@ +import {useEffect, useState} from 'react' +import {Linking} from 'react-native' + +const useLinking = () => { + const [deepLink, setDeepLink] = useState(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} From 99f1ab31cf5838fd2a5e4e83892b2cae5a009b6d Mon Sep 17 00:00:00 2001 From: "Jeferson S. Brito" <30840709+jeferson-sb@users.noreply.github.com> Date: Thu, 13 Mar 2025 16:46:20 -0300 Subject: [PATCH 2/2] refact: keep useLinking more focused on deep links --- README.md | 4 ++-- src/useLinking.test.ts | 22 ---------------------- src/useLinking.ts | 10 ++-------- 3 files changed, 4 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index 090abb54..26322fa0 100644 --- a/README.md +++ b/README.md @@ -156,12 +156,12 @@ 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. +useLinking can handle incoming your app's deeplinks through the [`Linking`](https://reactnative.dev/docs/linking) API. ```js import {useLinking} from '@react-native-community/hooks' -const { deepLink, openLinkInBrowser } = useLinking() +const { deepLink } = useLinking() console.log('Initial deep link is:', deepLink) ``` diff --git a/src/useLinking.test.ts b/src/useLinking.test.ts index e30c71d2..d3eeeafb 100644 --- a/src/useLinking.test.ts +++ b/src/useLinking.test.ts @@ -33,26 +33,4 @@ describe('useLinking', () => { 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() - }) }) diff --git a/src/useLinking.ts b/src/useLinking.ts index 9bb5b1a1..6ad87a9d 100644 --- a/src/useLinking.ts +++ b/src/useLinking.ts @@ -4,18 +4,12 @@ import {Linking} from 'react-native' const useLinking = () => { const [deepLink, setDeepLink] = useState(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)) + Linking.getInitialURL().then((url: string) => setDeepLink(url)) }, []) useEffect(() => { @@ -24,7 +18,7 @@ const useLinking = () => { return () => listener.remove() }, []) - return {openLinkInBrowser, openAppSettings, deepLink} + return {deepLink} } export {useLinking}