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:
- Centralized State Management:
- Maintains a single source of truth for plugin state
- Pub-Sub Architecture:
- Allows components to subscribe to specific state changes
- Type-Safe Implementation:
- Supports TypeScript generics for type validation
Why It Matters:
- Component Communication:
- Facilitates data flow between disconnected plugin components
- State Synchronization:
- Ensures consistent state across all parts of a plugin
- Lifecycle Integration:
- Makes plugin state responsive to viewer events and actions
How to Implement:
- Store Creation:
- Initialize the store with
createStore<StoreProps>({})
inside a plugin function - Use React's
useMemo
to prevent unnecessary recreation
- Initialize the store with
- State Management:
- Update store values with
store.update('key', value)
- Retrieve values using
store.get('key')
- Update store values with
- State Subscription:
- Subscribe to changes with
store.subscribe('key', callback)
- Unsubscribe when components unmount using
store.unsubscribe('key', callback)
- Subscribe to changes with
Store API:
-
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
-
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:
- Scenario:
- Creating a plugin to track and display PDF viewing statistics
- Application:
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,
};
};
- Result:
- A plugin that tracks and displays time spent on each page, using store to share state between event handlers and the UI component
Connections:
- Related Concepts:
- React PDF Viewer Plugin Lifecycle Methods: Methods that can update and use store data
- React PDF Viewer Custom Plugins: Implementation patterns using createStore
- Broader Concepts:
- State Management in React: Various approaches for managing component state
- Publish-Subscribe Pattern: Design pattern implemented by the store
References:
- Primary Source:
- React PDF Viewer Plugin Development Documentation
- Additional Resources:
- React State Management Patterns
- TypeScript Generic Types Documentation
Tags:
#react #pdf #state-management #store #plugin-development #typescript #pub-sub
Connections:
Sources:
- From: Version1