Show search results in a sidebar

In this example, we will create a sidebar that highlights search results found in a document. To make the example simple, the sidebar has four different parts:
In order to give you an idea of what we are going to do, play around with the following demo:
The search plugin provides the APIs that help us accomplish the example. As usual, we create a plugin instance and register it with the main `Viewer` component:
import { Viewer } from '@react-pdf-viewer/core';
import { searchPlugin } from '@react-pdf-viewer/search';
// Create an instance of the plugin
const searchPluginInstance = searchPlugin();
// Render
<div style={{ display: 'flex' }}>
<div
style={{
borderRight: '1px solid rgba(0, 0, 0, .2)',
flex: '0 0 15rem',
width: '15rem',
}}
>
{/* The sidebar goes here */}
</div>
<div style={{ flex: 1 }}>
<Viewer plugins={[searchPluginInstance]} />
</div>
</div>;
The layout is organized as a flexbox element which consists of the sidebar and main viewer located at the left and right sides, respectively.
The created plugin instance provides the `Search` component that can be used to build a custom search control.
const { Search } = searchPluginInstance;
// The sidebar
<Search>
{
(renderSearchProps: RenderSearchProps) => (
// ...
)
}
</Search>
The `RenderSearchProps` type provides useful properties and functions for the sidebar, including
PropertyTypeDescription
`currentMatch``number`The index of current match
`jumpToMatch``Function`Jump to the given match
`jumpToNextMatch``Function`Jump to the next match
`jumpToPreviousMatch``Function`Jump to the previous match
`keyword``string`The current keyword
`search``Function`Perform the search with current `keyword`
`setKeyword``Function`Set the current keyword
Let's see how we can use those properties to build four parts mentioned at the top of the page.

Providing a keyword

I am going to use the `TextBox` component provided by the `core` package, but it is up to you to use a normal text input.
import { TextBox } from '@react-pdf-viewer/core';
<TextBox placeholder="Enter to search" value={keyword} onChange={setKeyword} onKeyDown={handleSearchKeyDown} />;
Instead of adding a search button, the `placeholder` property provides good guidance for users to know how to search for the keyword. The `handleSearchKeyDown` will perform the `search` function when users press the `Enter` key:
const handleSearchKeyDown = (e: React.KeyboardEvent) => {
if (e.key === 'Enter' && keyword) {
search();
}
};
In order to search for a given keyword, the `search` function has to query the content of pages. Behind the scenes, the contents are queried for only one time, and is cached for the next search. However, that task will take time depending how big the document is.
Hence, it returns a `Promise` that you can update the results when the task is completed:
import { Match } from '@react-pdf-viewer/search';
const [matches, setMatches] = React.useState<Match[]>([]);
search().then((matches) => {
setMatches(matches);
});
Don't worry about the `Match` type as we will explore it in the next section.

Displaying the number of matches

As the `matches` state is updated after executing the `search` function, it's easy to display the total number of matches: `matches.length`.

Jumping to the previous and next match

The `jumpToPreviousMatch` and `jumpToNextMatch` functions are exactly what we need for.
import { MinimalButton } from '@react-pdf-viewer/core';
import { NextIcon, PreviousIcon } from '@react-pdf-viewer/search';
{
/* Navigate to the previous match */
}
<MinimalButton onClick={jumpToPreviousMatch}>
<PreviousIcon />
</MinimalButton>;
{
/* Navigate to the next match */
}
<MinimalButton onClick={jumpToNextMatch}>
<NextIcon />
</MinimalButton>;
Of course, you can use a normal `button` that handles the `onClick` event. But the packages have icons and components that have the same look and feel of the `Viewer` and other plugins' components.

Displaying the list of matches

By looping over the `matches`, we can display information of each match:
matches.map((match, index) => {
// You can check if the match is the current one
// and then distinguish it with other matches
const isCurrentMatch = currentMatch === index + 1;
return (
<div
{/* Jump to the match in the containing page */}
onClick={() => jumpToMatch(index + 1)}
>
{/* The index of match */}
{index + 1}
{/* The page contains the match */}
{match.pageIndex + 1}
</div>
);
}
It's worth noting that the `jumpToMatch` function accepts a single one-based index. Calling `jumpToMatch(1)` will jump to the first match.
The final piece of the example is to extract the text of the page that contains the keyword. The `Match` type contains the following properties:
PropertyTypeDescription
`pageText``string`The raw text of page
`startIndex``number`The index of character that starts the match
`endIndex``number`The index of character that ends the match
The following diagram denotes the meaning of the `startIndex` and `endIndex` properties:
startIndex endIndex
| |
▼ ▼
....[ keyword ]....
To extract the sample text that contains the `keyword`, we can construct the words before and after the `keyword`:
const wordsBefore = match.pageText.substr(match.startIndex - 20, 20);
let words = wordsBefore.split(' ');
words.shift();
const begin = words.length === 0 ? wordsBefore : words.join(' ');
const wordsAfter = match.pageText.substr(match.endIndex, 60);
words = wordsAfter.split(' ');
words.pop();
const end = words.length === 0 ? wordsAfter : words.join(' ');
And then render the sample text:
<div>
{/* The words before the keyword */}
{begin}
{/* Highlight the keyword */}
<span style={{ backgroundColor: 'rgb(255, 255, 0)' }}>
{match.pageText.substring(match.startIndex, match.endIndex)}
</span>
{/* The words after the keyword */}
{end}
</div>
There are still rooms for improvements such as automatically scrolling to the match item in the sidebar when you navigate between matches.
However, the example shows us how powerful the search plugin is. You are completely free to build your own search interface.
If you want to integrate the search results to the default layout, please visit this example.

See also