Open a confirmation modal when users click a link

As you know, a page consists of different layers including canvas, text and annotations. Links are part of the annotation layer. The `Viewer` component renders links as usual `a` elements.
Clicking them brings users to the associated website. This example demonstrates how we can prevent that default behavior. Instead of taking users to the target website directly, we will display a confirmation modal asking if users really want to the target website. This kind of user experience is used in a lot of websites nowadays.
The idea of implementation is quite straightforward. We can do it in three steps:

Find all link elements

The steps mentioned above will be managed by creating a custom plugin, `openLinksPlugin` for example. It is possible for a plugin to listen to the event when the annotation layer is rendered completely.
import type { Plugin, PluginOnAnnotationLayerRender } from '@react-pdf-viewer/core';
const openLinksPlugin = (): Plugin => {
const findLinks = (e: PluginOnAnnotationLayerRender) => {
// Find links
};
return {
onAnnotationLayerRender: findLinks,
};
};
The `PluginOnAnnotationLayerRender` type consists of the following properties
PropertyTypeDescription
`annotations``PdfJs.Annotation[]`List of annotations in the page
`container``HTMLElement`The container element that contains annotation elements
`pageIndex``number`The current page number
`rotation``number`The current rotation number
`scale``number`The current scale number
Each link annotation is an `a` element with the `data-target="external"` attribute. So we can query all link elements in the page easily:
const findLinks = (e: PluginOnAnnotationLayerRender) => {
e.container.querySelectorAll('a[data-target="external"]').forEach((link) => {
link.addEventListener('click', handleClick);
});
};
Don't worry about the `handleClick` handler. We will talk about it in the next section.

Handle the `click` event

Like many built-in plugins, the internal states of a plugin are usually managed by a store. In our example, we would like to know which link is clicked by users:
interface StoreProps {
clickedTarget?: string;
}
Let's create a store instance in the plugin entry point:
import * as React from 'react';
import { createStore } from '@react-pdf-viewer/core';
const openLinksPlugin = (): Plugin => {
const store = React.useMemo(() => createStore<StoreProps>({}), []);
};
Whenever a link is clicked, its target will be saved to our store. It can be done by using the store's `update` function:
// Handle the `click` event
const handleClick = (e: Event) => {
e.preventDefault();
const href = (e.target as HTMLLinkElement).href;
store.update('clickedTarget', href);
};
`e.preventDefault()` avoids the default behavior of clicking links. It prevents the browsers from redirecting to the target website.

Show the confirmation modal

We start this step by creating the `ConfirmationModal` component that manages the clicked link as an internal state. The store gives us the ability of synchronizing its states with a component states via the `subscribe` function:
import type { Store } from '@react-pdf-viewer/core';
const ConfirmationModal: React.FC<{
store: Store<StoreProps>;
}> = ({ store }) => {
const [target, setTarget] = React.useState('');
const handleTargetClicked = (clickedTarget: string) => {
setTarget(clickedTarget);
};
React.useEffect(() => {
store.subscribe('clickedTarget', handleTargetClicked);
return () => {
store.unsubscribe('clickedTarget', handleTargetClicked);
};
}, []);
};
It's worth noting that we also use the store's `unsubscribe` function to stop the synchronization when the component is destroyed.
It's very easy to display a modal when the `target` state is defined:
import { Modal } from '@react-pdf-viewer/core';
const ConfirmationModal: React.FC<{
store: Store<StoreProps>;
}> = ({ store }) => {
return (
target && <Modal isOpened={true} closeOnClickOutside={false} closeOnEscape={false} content={renderContent} />
);
};
It's up to you to use any components that display a modal. The sample code above uses the `Modal` component provided by the `@react-pdf-viewer/core` package. The `renderContent` function shows the content of the modal which has at least two buttons. The first button is to cancel the confirmation. The second one is to confirm and will bring users to the target website.
import { PrimaryButton, MinimalButton } from '@react-pdf-viewer/core';
const ConfirmationModal: React.FC<{
store: Store<StoreProps>;
}> = ({ store }) => {
const handleCancel = () => {
setTarget('');
};
const handleConfirm = () => {
setTarget('');
window.open(target, '_blank');
};
const renderContent = () => (
// ...
<div style={{ marginRight: '0.25rem' }}>
<MinimalButton onClick={handleCancel}>No</MinimalButton>
</div>
<PrimaryButton onClick={handleConfirm}>Yes</PrimaryButton>
);
};
The final part of our journey is how to attach the modal to the `Viewer` component. The plugin allows to do that via the `renderViewer` property:
import type { RenderViewer, Slot } from '@react-pdf-viewer/core';
const openLinksPlugin = (): Plugin => {
const renderViewer = (renderViewerProps: RenderViewer): Slot => {
const currentSlot = renderViewerProps.slot;
if (currentSlot.subSlot) {
currentSlot.subSlot.children = (
<>
<ConfirmationModal store={store} />
{currentSlot.subSlot.children}
</>
);
}
return currentSlot;
};
return {
renderViewer,
};
};
Here is the live demo:

See also