CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-react-pdf

Display PDFs in your React app as easily as if they were images.

Pending
Overview
Eval results
Files

context-hooks.mddocs/

Context Hooks

React hooks for accessing PDF context data and state within component trees, enabling custom components to integrate with the react-pdf ecosystem.

Capabilities

useDocumentContext Hook

Provides access to document-level context data including PDF document proxy, link service, and page registration functions.

/**
 * Hook to access document context data
 * Must be used within a Document component or provider
 * @returns Document context object or null if outside Document
 */
function useDocumentContext(): DocumentContextType;

interface DocumentContextType {
  /** Loaded PDF document proxy */
  pdf?: PDFDocumentProxy | false;
  /** Link service for handling internal and external links */
  linkService: LinkService;
  /** Function to register page elements for navigation */
  registerPage: (pageIndex: number, ref: HTMLDivElement) => void;
  /** Function to unregister page elements */
  unregisterPage: (pageIndex: number) => void;
  /** Item click handler for navigation */
  onItemClick?: (args: OnItemClickArgs) => void;
  /** Image resources path for annotations */
  imageResourcesPath?: string;
  /** Document rendering mode */    
  renderMode?: RenderMode;
  /** Document scale factor */
  scale?: number;
  /** Document rotation */
  rotate?: number | null;
} | null;

Usage Examples:

import { useDocumentContext } from "react-pdf";

// Custom component accessing document info
function DocumentInfo() {
  const documentContext = useDocumentContext();
  
  if (!documentContext?.pdf) {
    return <div>No document loaded</div>;
  }
  
  const { pdf } = documentContext;
  
  return (
    <div className="document-info">
      <h3>Document Information</h3>
      <p>Total Pages: {pdf.numPages}</p>
      <p>PDF.js Version: {pdf.pdfInfo?.PDFFormatVersion}</p>
    </div>
  );
}

// Custom navigation component
function CustomNavigation() {
  const documentContext = useDocumentContext();
  const [currentPage, setCurrentPage] = useState(1);
  
  if (!documentContext?.pdf) return null;
  
  const { pdf, onItemClick } = documentContext;
  
  const handlePageChange = (pageNumber) => {
    setCurrentPage(pageNumber);
    if (onItemClick) {
      onItemClick({
        pageIndex: pageNumber - 1,
        pageNumber: pageNumber
      });
    }
  };
  
  return (
    <div className="custom-navigation">
      <button 
        onClick={() => handlePageChange(Math.max(1, currentPage - 1))}
        disabled={currentPage === 1}
      >
        Previous
      </button>
      <span>Page {currentPage} of {pdf.numPages}</span>
      <button 
        onClick={() => handlePageChange(Math.min(pdf.numPages, currentPage + 1))}
        disabled={currentPage === pdf.numPages}
      >
        Next
      </button>
    </div>
  );
}

// Document metadata extractor
function DocumentMetadata() {
  const documentContext = useDocumentContext();
  const [metadata, setMetadata] = useState(null);
  
  useEffect(() => {
    if (documentContext?.pdf) {
      documentContext.pdf.getMetadata().then(({ info, metadata }) => {
        setMetadata({ info, metadata });
      });
    }
  }, [documentContext?.pdf]);
  
  if (!metadata) return <div>Loading metadata...</div>;
  
  return (
    <div className="metadata">
      <h3>Document Metadata</h3>
      <p>Title: {metadata.info.Title || 'Untitled'}</p>
      <p>Author: {metadata.info.Author || 'Unknown'}</p>
      <p>Subject: {metadata.info.Subject || 'None'}</p>
      <p>Creator: {metadata.info.Creator || 'Unknown'}</p>
      <p>Producer: {metadata.info.Producer || 'Unknown'}</p>
      <p>Creation Date: {metadata.info.CreationDate || 'Unknown'}</p>
    </div>
  );
}

usePageContext Hook

Provides access to page-level context data including page proxy, dimensions, and rendering settings.

/**
 * Hook to access page context data
 * Must be used within a Page component or provider
 * @returns Page context object or null if outside Page
 */
function usePageContext(): PageContextType;

interface PageContextType {
  /** PDF page proxy object */
  page: PDFPageProxy | false | undefined;
  /** Zero-based page index */
  pageIndex: number;
  /** One-based page number */
  pageNumber: number;
  /** Page scale factor */
  scale: number;
  /** Page rotation in degrees */
  rotate: number;
  /** Canvas background color */
  canvasBackground?: string;
  /** Custom text renderer function */
  customTextRenderer?: CustomTextRenderer;
  /** Device pixel ratio */
  devicePixelRatio?: number;
  /** Whether forms should be rendered */
  renderForms: boolean;
  /** Whether text layer should be rendered */
  renderTextLayer: boolean;
  /** Internal CSS class name */
  _className?: string;
} | null;

Usage Examples:

import { usePageContext } from "react-pdf";

// Custom page overlay component
function PageOverlay() {
  const pageContext = usePageContext();
  
  if (!pageContext?.page) return null;
  
  const { page, pageNumber, scale, rotate } = pageContext;
  const viewport = page.getViewport({ scale, rotation: rotate });
  
  return (
    <div 
      className="page-overlay"
      style={{
        position: 'absolute',
        top: 0,
        left: 0,
        width: viewport.width,
        height: viewport.height,
        pointerEvents: 'none',
        border: '2px solid red',
        zIndex: 10
      }}
    >
      <div className="page-label">
        Page {pageNumber}
      </div>
    </div>
  );
}

// Page dimensions display
function PageDimensions() {
  const pageContext = usePageContext();
  
  if (!pageContext?.page) return null;
  
  const { page, scale, rotate } = pageContext;
  const viewport = page.getViewport({ scale: 1, rotation: rotate });
  const scaledViewport = page.getViewport({ scale, rotation: rotate });
  
  return (
    <div className="page-dimensions">
      <h4>Page Dimensions</h4>
      <p>Original: {viewport.width.toFixed(1)} × {viewport.height.toFixed(1)}</p>
      <p>Scaled: {scaledViewport.width.toFixed(1)} × {scaledViewport.height.toFixed(1)}</p>
      <p>Scale: {(scale * 100).toFixed(0)}%</p>
      <p>Rotation: {rotate}°</p>
    </div>
  );
}

// Custom text highlighter using page context
function TextHighlighter({ searchTerm }) {
  const pageContext = usePageContext();
  const [textItems, setTextItems] = useState([]);
  
  useEffect(() => {
    if (pageContext?.page) {
      pageContext.page.getTextContent().then(textContent => {
        setTextItems(textContent.items);
      });
    }
  }, [pageContext?.page]);
  
  if (!pageContext?.page || !searchTerm) return null;
  
  const { page, scale, rotate } = pageContext;
  const viewport = page.getViewport({ scale, rotation: rotate });
  
  return (
    <div className="text-highlights">
      {textItems
        .filter(item => item.str.toLowerCase().includes(searchTerm.toLowerCase()))
        .map((item, index) => {
          const [x, y, width, height] = item.transform;
          return (
            <div
              key={index}
              className="highlight"
              style={{
                position: 'absolute',
                left: x * scale,
                top: viewport.height - (y * scale),
                width: item.width * scale,
                height: item.height * scale,
                backgroundColor: 'yellow',
                opacity: 0.3,
                pointerEvents: 'none'
              }}
            />
          );
        })}
    </div>
  );
}

useOutlineContext Hook

Provides access to outline-specific context data for navigation and interaction handling.

/**
 * Hook to access outline context data
 * Must be used within an Outline component or provider
 * @returns Outline context object or null if outside Outline
 */
function useOutlineContext(): OutlineContextType;

interface OutlineContextType {
  /** Item click handler for outline navigation */
  onItemClick?: (args: OnItemClickArgs) => void;
} | null;

Usage Examples:

import { useOutlineContext } from "react-pdf";

// Custom outline item component
function CustomOutlineItem({ item, level = 0 }) {
  const outlineContext = useOutlineContext();
  
  const handleClick = () => {
    if (outlineContext?.onItemClick && item.dest) {
      // Note: In real implementation, you'd need to resolve dest to page number
      outlineContext.onItemClick({
        dest: item.dest,
        pageIndex: 0, // Would need to resolve from dest
        pageNumber: 1  // Would need to resolve from dest
      });
    }
  };
  
  return (
    <div className="custom-outline-item">
      <div 
        className={`outline-title level-${level}`}
        onClick={handleClick}
        style={{
          paddingLeft: level * 20,
          cursor: 'pointer',
          fontWeight: item.bold ? 'bold' : 'normal',
          fontStyle: item.italic ? 'italic' : 'normal',
          color: item.color ? `rgb(${item.color.join(',')})` : 'inherit'
        }}
      >
        {item.title}
      </div>
      {item.items && item.items.length > 0 && (
        <div className="outline-children">
          {item.items.map((child, index) => (
            <CustomOutlineItem 
              key={index} 
              item={child} 
              level={level + 1} 
            />
          ))}
        </div>
      )}
    </div>
  );
}

// Outline with custom interaction
function InteractiveOutline() {
  const outlineContext = useOutlineContext();
  const [expandedItems, setExpandedItems] = useState(new Set());
  
  const toggleExpanded = (itemId) => {
    const newExpanded = new Set(expandedItems);
    if (newExpanded.has(itemId)) {
      newExpanded.delete(itemId);
    } else {
      newExpanded.add(itemId);
    }
    setExpandedItems(newExpanded);
  };
  
  return (
    <div className="interactive-outline">
      {/* Custom outline rendering with expand/collapse */}
    </div>
  );
}

Combined Context Usage

Using multiple context hooks together for complex custom components.

interface CombinedContextUsage {
  /** Using document and page contexts together */
  documentAndPageContext?: () => void;
  /** Using all available contexts */
  allContexts?: () => void;
}

Usage Examples:

// Component using both document and page contexts
function PageWithDocumentInfo() {
  const documentContext = useDocumentContext();
  const pageContext = usePageContext();
  
  if (!documentContext?.pdf || !pageContext?.page) {
    return <div>Loading...</div>;
  }
  
  const { pdf } = documentContext;
  const { pageNumber, scale } = pageContext;
  
  return (
    <div className="page-with-info">
      <div className="page-header">
        <h3>Document: {pdf.numPages} pages</h3>
        <p>Current: Page {pageNumber} at {(scale * 100).toFixed(0)}%</p>
      </div>
      {/* Page content would be rendered here */}
    </div>
  );
}

// Full-featured PDF viewer using all contexts
function FullFeaturedViewer() {
  const documentContext = useDocumentContext();
  const pageContext = usePageContext();
  const outlineContext = useOutlineContext();
  
  const [showMetadata, setShowMetadata] = useState(false);
  const [showOutline, setShowOutline] = useState(false);
  
  return (
    <div className="full-viewer">
      <div className="toolbar">
        <button onClick={() => setShowMetadata(!showMetadata)}>
          {showMetadata ? 'Hide' : 'Show'} Metadata
        </button>
        <button onClick={() => setShowOutline(!showOutline)}>
          {showOutline ? 'Hide' : 'Show'} Outline
        </button>
        
        {pageContext && (
          <span>
            Page {pageContext.pageNumber} • 
            Scale {(pageContext.scale * 100).toFixed(0)}%
          </span>
        )}
      </div>
      
      <div className="viewer-content">
        {showOutline && (
          <div className="sidebar">
            {/* Custom outline component */}
          </div>
        )}
        
        <div className="main-content">
          {showMetadata && (
            <div className="metadata-panel">
              {/* Document metadata */}
            </div>
          )}
          {/* Page content */}
        </div>
      </div>
    </div>
  );
}

// Context validation helper
function useValidatedContexts() {
  const documentContext = useDocumentContext();
  const pageContext = usePageContext();
  
  const isDocumentReady = Boolean(documentContext?.pdf);
  const isPageReady = Boolean(pageContext?.page);
  
  return {
    documentContext,
    pageContext,
    isDocumentReady,
    isPageReady,
    isFullyReady: isDocumentReady && isPageReady
  };
}

function ContextAwareComponent() {
  const { 
    documentContext, 
    pageContext, 
    isFullyReady 
  } = useValidatedContexts();
  
  if (!isFullyReady) {
    return <div>Contexts not ready</div>;
  }
  
  return (
    <div>
      {/* Component that requires both contexts */}
    </div>
  );
}

Context Error Handling

Proper error handling and validation when using context hooks.

interface ContextErrorHandling {
  /** Validate context availability */
  validateContext?: () => boolean;
  /** Handle missing context gracefully */
  handleMissingContext?: () => React.ReactElement;
}

Usage Examples:

// Context validation wrapper
function withContextValidation(Component, requiredContexts = []) {
  return function ValidatedComponent(props) {
    const documentContext = useDocumentContext();
    const pageContext = usePageContext();
    const outlineContext = useOutlineContext();
    
    const contexts = {
      document: documentContext,
      page: pageContext,
      outline: outlineContext
    };
    
    // Check if all required contexts are available
    const missingContexts = requiredContexts.filter(
      contextName => !contexts[contextName]
    );
    
    if (missingContexts.length > 0) {
      console.warn(
        `Component requires ${requiredContexts.join(', ')} context(s), ` +
        `but ${missingContexts.join(', ')} ${missingContexts.length === 1 ? 'is' : 'are'} missing`
      );
      return <div>Missing required PDF context</div>;
    }
    
    return <Component {...props} />;
  };
}

// Usage
const ValidatedPageInfo = withContextValidation(
  function PageInfo() {
    const pageContext = usePageContext();
    return <div>Page {pageContext.pageNumber}</div>;
  },
  ['page']
);

// Safe context hook with fallbacks
function useSafeDocumentContext() {
  const context = useDocumentContext();
  
  return {
    pdf: context?.pdf || null,
    numPages: context?.pdf?.numPages || 0,
    isReady: Boolean(context?.pdf),
    linkService: context?.linkService || null,
    registerPage: context?.registerPage || (() => {}),
    unregisterPage: context?.unregisterPage || (() => {})
  };
}

function SafeDocumentInfo() {
  const { pdf, numPages, isReady } = useSafeDocumentContext();
  
  if (!isReady) {
    return <div>Document not ready</div>;
  }
  
  return <div>Document has {numPages} pages</div>;
}

Install with Tessl CLI

npx tessl i tessl/npm-react-pdf

docs

context-hooks.md

document-management.md

index.md

navigation-components.md

page-rendering.md

tile.json