#atom

Subtitle:

Core state management utility for building custom plugins in React PDF Viewer


Core Idea:

The createStore function provides a lightweight state management solution for React PDF Viewer plugins, enabling data sharing between plugin components and synchronization with the main viewer component through a publish-subscribe pattern.


Key Principles:

  1. Centralized State Management:
    • Maintains a single source of truth for plugin state
  2. Pub-Sub Architecture:
    • Allows components to subscribe to specific state changes
  3. Type-Safe Implementation:
    • Supports TypeScript generics for type validation

Why It Matters:


How to Implement:

  1. Store Creation:
    • Initialize the store with createStore<StoreProps>({}) inside a plugin function
    • Use React's useMemo to prevent unnecessary recreation
  2. State Management:
    • Update store values with store.update('key', value)
    • Retrieve values using store.get('key')
  3. State Subscription:
    • Subscribe to changes with store.subscribe('key', callback)
    • Unsubscribe when components unmount using store.unsubscribe('key', callback)

Store API:

  1. Core Methods:

    • get<T>(key: string): T
      • Retrieves the current value for the given key
      • Returns undefined if key doesn't exist
    • update<T>(key: string, value: T): void
      • Updates the store with a new value for the specified key
      • Triggers notifications to all subscribers for that key
    • subscribe<T>(key: string, callback: (value: T) => void): void
      • Registers a callback to be called when the specified key changes
      • Callback receives the new value as a parameter
    • unsubscribe<T>(key: string, callback: (value: T) => void): void
      • Removes a previously registered callback for the specified key
      • Important for preventing memory leaks
  2. Usage Pattern:

    • Typically initialized in a plugin's main function
    • Passed to components that need access to shared state
    • Used with React's useEffect for subscription management

Example:

import * as React from 'react';
import { createStore, Plugin } from '@react-pdf-viewer/core';

interface StoreProps {
  totalPages?: number;
  currentPage?: number;
  timeSpent?: Record<number, number>;
}

const viewingStatsPlugin = () => {
  // Create a store to manage state
  const store = React.useMemo(() => createStore<StoreProps>({
    timeSpent: {},
  }), []);
  
  // Track time spent per page
  let startTime = Date.now();
  
  // Component to display stats
  const ViewingStats = () => {
    const [stats, setStats] = React.useState<StoreProps>({
      totalPages: 0,
      currentPage: 0,
      timeSpent: {},
    });
    
    React.useEffect(() => {
      // Update stats when store changes
      const handleStoreChange = (key: keyof StoreProps, value: any) => {
        setStats(prev => ({ ...prev, [key]: value }));
      };
      
      // Subscribe to store changes
      store.subscribe('totalPages', handleStoreChange.bind(null, 'totalPages'));
      store.subscribe('currentPage', handleStoreChange.bind(null, 'currentPage'));
      store.subscribe('timeSpent', handleStoreChange.bind(null, 'timeSpent'));
      
      return () => {
        // Unsubscribe when component unmounts
        store.unsubscribe('totalPages', handleStoreChange.bind(null, 'totalPages'));
        store.unsubscribe('currentPage', handleStoreChange.bind(null, 'currentPage'));
        store.unsubscribe('timeSpent', handleStoreChange.bind(null, 'timeSpent'));
      };
    }, []);
    
    return (
      <div className="viewing-stats">
        <p>Page {stats.currentPage} of {stats.totalPages}</p>
        <p>Time spent on current page: {
          stats.currentPage && stats.timeSpent && stats.timeSpent[stats.currentPage] 
            ? Math.round(stats.timeSpent[stats.currentPage] / 1000) 
            : 0
        } seconds</p>
      </div>
    );
  };
  
  return {
    onDocumentLoad: (e) => {
      store.update('totalPages', e.doc.numPages);
    },
    
    onPageChange: (e) => {
      const now = Date.now();
      const oldPage = store.get('currentPage');
      const timeSpent = store.get('timeSpent') || {};
      
      // Update time spent on previous page
      if (oldPage) {
        const prevTime = timeSpent[oldPage] || 0;
        timeSpent[oldPage] = prevTime + (now - startTime);
        store.update('timeSpent', { ...timeSpent });
      }
      
      // Update current page
      store.update('currentPage', e.currentPage);
      startTime = now;
    },
    
    ViewingStats,
  };
};

Connections:


References:

  1. Primary Source:
    • React PDF Viewer Plugin Development Documentation
  2. Additional Resources:
    • React State Management Patterns
    • TypeScript Generic Types Documentation

Tags:

#react #pdf #state-management #store #plugin-development #typescript #pub-sub


Connections:


Sources: