Skip to content
Add a Custom Search Bar on the Top Bar

An image of Add a Custom Search Bar on the Top Bar

The application needs a custom search bar on the Viewer’s top bar instead of using the default search tool.

  • To match your app’s design system
  • To add debounce or special search logic
  • To place the search bar directly in the top bar

The useSearchContext provides a function to search and highlight the first occurrence of a text in a PDF.

Here are the functions we’re using for this example

NameObjective
currentMatchPositionReturn the index (1-based) of the currently highlighted match within the total search results. Help track the user’s current position among matches
loadingIndicate whether a search operation is currently in progress
nextMatchNavigates to and highlights the next search result in the document. Wraps around to the first match if the end is reached
prevMatchNavigate to and highlights the previous search result in the document. Wrap around to the last match if the beginning is reached
searchExecute a search for the specified text within the currently loaded PDF document.
setSearchUpdate the search query value without immediately executing a new search
totalMatchesIndicate the total number of text matches found in the PDF document for the current search term

After integrating the useSearchContext hook into a custom search UI, you may use createPortal to add the component into the top bar of the Viewer.

NameObjective
createPortalRender the given React content into a specific container
import { useState, useCallback, useRef, useEffect } from "react";
import {
RPConfig,
RPProvider,
RPDefaultLayout,
RPPages,
useSearchContext,
} from "@pdf-viewer/react";
import React from "react";
import { createPortal } from "react-dom";
const CustomSearch = () => {
const {
search,
setSearch,
currentMatchPosition,
totalMatches,
nextMatch,
prevMatch,
loading,
} = useSearchContext();
const [searchValue, setSearchValue] = useState(search);
const handleChange = useCallback(
(e) => {
setSearchValue(e.target.value);
},
[]
);
// Debounce search input
useEffect(() => {
const timer = setTimeout(() => {
setSearch(searchValue);
}, 500);
return () => clearTimeout(timer);
}, [searchValue,searchValue]);
const handleSubmit = useCallback(() => {
setSearch(searchValue);
}, [searchValue, setSearch]);
const handleNext = useCallback(() => {
nextMatch();
}, [nextMatch]);
const handlePrev = useCallback(() => {
prevMatch();
}, [prevMatch]);
return (
<div>
<input value={searchValue} onChange={handleChange} />
<button onClick={handleSubmit}>Submit</button>
<span>
{currentMatchPosition} / {totalMatches}
</span>
<button onClick={handlePrev}>Prev</button>
<button onClick={handleNext}>Next</button>
{loading && <div>searching...</div>}
</div>
);
};
export const AppPdfViewerSearch = () => {
const ref = useRef(null);
const [target, setTarget] = useState(null);
useEffect(() => {
const elemTopBarLeft = ref.current?.querySelector('[data-rp="topBarLeft"]');
if (elemTopBarLeft) {
const wrapper = document.createElement("div");
ref.current = wrapper;
elemTopBarLeft.prepend(wrapper);
setTarget(wrapper);
}
}, []);
return (
<>
<RPConfig licenseKey="YOUR_LICENSE_KEY">
<RPProvider src="https://cdn.codewithmosh.com/image/upload/v1721763853/guides/web-roadmap.pdf">
<div ref={ref}>
<RPDefaultLayout
slots={{ searchTool: false, pageNavigationTool: false }}
>
{target && createPortal(<CustomSearch />, target)}
<RPPages />
</RPDefaultLayout>
</div>
</RPProvider>
</RPConfig>
</>
);
};