diff --git a/README.md b/README.md index 2e63799..34d970c 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ npm i --save react-native-hyperlink | `linkText` | A string or a func to replace parsed text | `oneOfType([ string, func ])` | | `onPress` | Func to handle click over a clickable text with parsed text as arg | `func` | | `onLongPress` | Func to handle long click over a clickable text with parsed text as arg | `func` | +| `onLinkError` | Func to handle errors when `Linking.openURL` fails (used with `linkDefault`) | `func` | |`linkDefault`|A platform specific fallback to handle `onPress`. Uses [Linking](https://facebook.github.io/react-native/docs/linking.html). Disabled by default | `bool` |`injectViewProps`| Func with url as a param to inject props to the clickable component | `func` | `i => ({})` diff --git a/src/Hyperlink.tsx b/src/Hyperlink.tsx index 9a951d8..a21f319 100644 --- a/src/Hyperlink.tsx +++ b/src/Hyperlink.tsx @@ -203,11 +203,14 @@ export default class extends Component { handleLink(url: string) { const urlObject = parse(url); - urlObject.protocol = urlObject.protocol.toLowerCase(); + urlObject.protocol = urlObject.protocol?.toLowerCase() ?? ''; const normalizedURL = format(urlObject); Linking.canOpenURL(normalizedURL).then((supported: boolean) => { - supported && Linking.openURL(normalizedURL); + if (!supported) return; + Linking.openURL(normalizedURL).catch(error => { + if (this.props.onLinkError) this.props.onLinkError(error); + }); }); } diff --git a/src/__tests__/index.test.tsx b/src/__tests__/index.test.tsx index cc5e2f4..b20149c 100644 --- a/src/__tests__/index.test.tsx +++ b/src/__tests__/index.test.tsx @@ -1,4 +1,4 @@ -import { render, fireEvent } from '@testing-library/react-native'; +import { render, fireEvent, waitFor } from '@testing-library/react-native'; import { Text, View, Linking, Platform } from 'react-native'; import Hyperlink from '../index'; @@ -662,6 +662,42 @@ describe('Hyperlink', () => { }); }); + describe('errors', () => { + // mdurl.parse returns protocol as null for url like this + const url = '//example.com'; + + it('should handle invalid link gracefully', () => { + expect(() => { + const { getByText } = render( + + Visit our site at {url} + , + ); + fireEvent.press(getByText(url)); + }).not.toThrow(); + }); + + it('should handle openURL fail gracefully and pass error', async () => { + const mockError = new Error('Failed to open'); + const mockOnError = jest.fn(); + jest.spyOn(Linking, 'openURL').mockRejectedValueOnce(mockError); + + const { getByText } = render( + + Visit our site at {url} + , + ); + fireEvent.press(getByText(url)); + + await waitFor(() => { + expect(mockOnError).toHaveBeenCalledWith(mockError); + }); + }); + }); + it('does not warn when passing `key` prop to outer component', () => { const consoleError = jest .spyOn(console, 'error') diff --git a/src/types.ts b/src/types.ts index c4d98f8..6cb2f48 100644 --- a/src/types.ts +++ b/src/types.ts @@ -27,6 +27,7 @@ export type HyperlinkProps = { linkText?: ((url: string) => string) | string; onPress?: (url: string, text?: string) => void; onLongPress?: (url: string, text?: string) => void; + onLinkError?: (error: unknown) => void; injectViewProps?: (url: string) => TextProps; style?: ViewProps['style']; children?: ReactElementWithType;