useDocumentSearch

Searches the document text: set a query, step through the matches, and let the SDK highlight them on the pages and scroll to the active one. Re-renders the component as the search progresses.

const {
  criteria,
  matches,
  activeMatchIndex,
  status,
  setSearchCriteria,
  goToNextMatch,
  goToPreviousMatch,
  goToMatch,
} = useDocumentSearch()

Every hook call shares the same search, so a search bar and a results list in different components stay in sync. Each loaded document starts with an empty query and every option off.

State

FieldTypeMeaning
criteriaSearchCriteriaThe query and the options, as last set.
matchesreadonly SearchMatch[]The matches in document order: page by page, and within a page paragraph by paragraph.
activeMatchIndexnumberThe index of the active match in matches, or -1 when no match is active.
statusSearchStatusHow far the search has gone through the document.

SearchCriteria fields:

FieldTypeMeaning
querystringThe text to find. '' means no search.
isCaseSensitivebooleanMatch the letter case of the query exactly.
isWholeWordbooleanSkip occurrences that continue a word on either side. Letters, digits, combining marks, and _ make up words.

SearchMatch fields:

FieldTypeMeaning
idstringIdentifies the match for React keys and goToMatch (session-scoped).
pageIdstringThe page the match is on, as the page hooks such as useScrollToPage accept it.
paragraphIdstringThe paragraph the match is in, as the block hooks such as useScrollToBlock accept it.
textstringThe matched text as it appears in the document.

SearchStatus is one of:

StatusMeaning
'idle'There is no query.
'searching'The criteria changed and the search hasn't gone through the whole document yet. matches grows meanwhile.
'complete'The whole document is searched. The matches keep following the edits. A page whose text fails to load is skipped.

Actions

  • setSearchCriteria(criteria) starts a search for new criteria. To change one field, spread criteria into the call: setSearchCriteria({ ...criteria, isWholeWord: true }).
    • A changed query is searched once typing pauses, so status is 'searching' with no matches for a moment.
    • Changing an option without changing the query searches at once.
    • The first match found becomes active and is scrolled to.
    • A query of '' drops the matches and every highlight.
    • Criteria equal to the current ones change nothing.
  • goToNextMatch() and goToPreviousMatch() activate the next or previous match and scroll to it, wrapping around at the ends. They do nothing when there are no matches.
  • goToMatch(matchId) activates the match with the given id and scrolls to it. It does nothing when no match has that id, such as one read before an edit removed it.

Behavior

  • The matches follow the document: edits, undo, and redo update them, and the active match stays on its occurrence.
  • matches is a new array only when the list changes, and an unchanged match keeps its object, so a results list can memoize its rows by match.
  • A match id comes from its paragraph and its position among the paragraph's matches. An edit that adds or removes an earlier match in the same paragraph moves the id to a neighboring match.

Highlight styling

The SDK paints the matches inside the page text. Style them through the searchMatch key of the theme prop on PdfProviders, or through the CSS variables on the viewport:

CSS variableDefaultWhat it colors
--search-match-backgroundrgba(255, 213, 0, 0.4)Every match.
--search-match-active-backgroundrgba(255, 149, 0, 0.55)The active match.
  • A classNames.searchMatch class styles the active match apart with a [data-active] selector.
  • A styles.searchMatch entry is written to the element as is: give lengths units ('4px', not 4).
  • Use paint properties only, such as background and color: a padding, margin, or border moves the document text.

Example

import { useDocumentSearch } from '@sdk-pdf/react'

function SearchBar() {
  const {
    criteria,
    matches,
    activeMatchIndex,
    status,
    setSearchCriteria,
    goToNextMatch,
    goToPreviousMatch,
  } = useDocumentSearch()

  return (
    <div>
      <input
        value={criteria.query}
        placeholder="Search"
        onChange={(event) =>
          setSearchCriteria({ ...criteria, query: event.target.value })
        }
        onKeyDown={(event) => {
          if (event.key === 'Enter')
            (event.shiftKey ? goToPreviousMatch : goToNextMatch)()
        }}
      />
      <label>
        <input
          type="checkbox"
          checked={criteria.isCaseSensitive}
          onChange={(event) =>
            setSearchCriteria({ ...criteria, isCaseSensitive: event.target.checked })
          }
        />
        Match case
      </label>
      <span>
        {matches.length > 0
          ? `${activeMatchIndex + 1} of ${matches.length}`
          : status === 'complete'
            ? 'No results'
            : ''}
      </span>
    </div>
  )
}

See also useScrollToBlock and Theming and customization.

On this page