Refactor to use MutableRefObject instead of RefObject

Replaces all instances of React.RefObject with React.MutableRefObject for refs that are mutated directly throughout the codebase. This change clarifies intent and improves type safety for refs that are updated outside of React's ref assignment.
This commit is contained in:
Ludy87 2025-09-26 11:58:03 +02:00
parent 3fa0535246
commit 57b2fcaf2c
No known key found for this signature in database
GPG Key ID: 92696155E0220F94
9 changed files with 30 additions and 30 deletions

View File

@ -23,7 +23,7 @@ interface PageThumbnailProps {
selectionMode: boolean; selectionMode: boolean;
movingPage: number | null; movingPage: number | null;
isAnimating: boolean; isAnimating: boolean;
pageRefs: React.RefObject<Map<string, HTMLDivElement>>; pageRefs: React.MutableRefObject<Map<string, HTMLDivElement>>;
onReorderPages: (sourcePageNumber: number, targetIndex: number, selectedPageIds?: string[]) => void; onReorderPages: (sourcePageNumber: number, targetIndex: number, selectedPageIds?: string[]) => void;
onTogglePage: (pageId: string) => void; onTogglePage: (pageId: string) => void;
onAnimateReorder: () => void; onAnimateReorder: () => void;

View File

@ -71,7 +71,7 @@ const ToolPicker = ({ selectedToolKey, onSelect, filteredTools, isSearching = fa
[visibleSections] [visibleSections]
); );
const scrollTo = (ref: React.RefObject<HTMLDivElement | null>) => { const scrollTo = (ref: React.MutableRefObject<HTMLDivElement | null>) => {
const container = scrollableRef.current; const container = scrollableRef.current;
const target = ref.current; const target = ref.current;
if (container && target) { if (container && target) {

View File

@ -13,7 +13,7 @@ interface FileManagerContextValue {
searchTerm: string; searchTerm: string;
selectedFiles: StirlingFileStub[]; selectedFiles: StirlingFileStub[];
filteredFiles: StirlingFileStub[]; filteredFiles: StirlingFileStub[];
fileInputRef: React.RefObject<HTMLInputElement | null>; fileInputRef: React.MutableRefObject<HTMLInputElement | null>;
selectedFilesSet: Set<FileId>; selectedFilesSet: Set<FileId>;
expandedFileIds: Set<FileId>; expandedFileIds: Set<FileId>;
fileGroups: Map<FileId, StirlingFileStub[]>; fileGroups: Map<FileId, StirlingFileStub[]>;

View File

@ -165,8 +165,8 @@ interface AddFileOptions {
*/ */
export async function addFiles( export async function addFiles(
options: AddFileOptions, options: AddFileOptions,
stateRef: React.RefObject<FileContextState>, stateRef: React.MutableRefObject<FileContextState>,
filesRef: React.RefObject<Map<FileId, File>>, filesRef: React.MutableRefObject<Map<FileId, File>>,
dispatch: React.Dispatch<FileContextAction>, dispatch: React.Dispatch<FileContextAction>,
lifecycleManager: FileLifecycleManager, lifecycleManager: FileLifecycleManager,
enablePersistence: boolean = false enablePersistence: boolean = false
@ -278,7 +278,7 @@ export async function consumeFiles(
inputFileIds: FileId[], inputFileIds: FileId[],
outputStirlingFiles: StirlingFile[], outputStirlingFiles: StirlingFile[],
outputStirlingFileStubs: StirlingFileStub[], outputStirlingFileStubs: StirlingFileStub[],
filesRef: React.RefObject<Map<FileId, File>>, filesRef: React.MutableRefObject<Map<FileId, File>>,
dispatch: React.Dispatch<FileContextAction> dispatch: React.Dispatch<FileContextAction>
): Promise<FileId[]> { ): Promise<FileId[]> {
if (DEBUG) console.log(`📄 consumeFiles: Processing ${inputFileIds.length} input files, ${outputStirlingFiles.length} output files with pre-created stubs`); if (DEBUG) console.log(`📄 consumeFiles: Processing ${inputFileIds.length} input files, ${outputStirlingFiles.length} output files with pre-created stubs`);
@ -357,7 +357,7 @@ export async function consumeFiles(
async function restoreFilesAndCleanup( async function restoreFilesAndCleanup(
filesToRestore: { file: File; record: StirlingFileStub }[], filesToRestore: { file: File; record: StirlingFileStub }[],
fileIdsToRemove: FileId[], fileIdsToRemove: FileId[],
filesRef: React.RefObject<Map<FileId, File>>, filesRef: React.MutableRefObject<Map<FileId, File>>,
indexedDB?: { deleteFile: (fileId: FileId) => Promise<void> } | null indexedDB?: { deleteFile: (fileId: FileId) => Promise<void> } | null
): Promise<void> { ): Promise<void> {
// Remove files from filesRef // Remove files from filesRef
@ -406,7 +406,7 @@ export async function undoConsumeFiles(
inputFiles: File[], inputFiles: File[],
inputStirlingFileStubs: StirlingFileStub[], inputStirlingFileStubs: StirlingFileStub[],
outputFileIds: FileId[], outputFileIds: FileId[],
filesRef: React.RefObject<Map<FileId, File>>, filesRef: React.MutableRefObject<Map<FileId, File>>,
dispatch: React.Dispatch<FileContextAction>, dispatch: React.Dispatch<FileContextAction>,
indexedDB?: { saveFile: (file: File, fileId: FileId, existingThumbnail?: string) => Promise<any>; deleteFile: (fileId: FileId) => Promise<void> } | null indexedDB?: { saveFile: (file: File, fileId: FileId, existingThumbnail?: string) => Promise<any>; deleteFile: (fileId: FileId) => Promise<void> } | null
): Promise<void> { ): Promise<void> {
@ -468,8 +468,8 @@ export async function undoConsumeFiles(
export async function addStirlingFileStubs( export async function addStirlingFileStubs(
stirlingFileStubs: StirlingFileStub[], stirlingFileStubs: StirlingFileStub[],
options: { insertAfterPageId?: string; selectFiles?: boolean } = {}, options: { insertAfterPageId?: string; selectFiles?: boolean } = {},
stateRef: React.RefObject<FileContextState>, stateRef: React.MutableRefObject<FileContextState>,
filesRef: React.RefObject<Map<FileId, File>>, filesRef: React.MutableRefObject<Map<FileId, File>>,
dispatch: React.Dispatch<FileContextAction>, dispatch: React.Dispatch<FileContextAction>,
_lifecycleManager: FileLifecycleManager _lifecycleManager: FileLifecycleManager
): Promise<StirlingFile[]> { ): Promise<StirlingFile[]> {

View File

@ -15,8 +15,8 @@ import {
* Create stable selectors using stateRef and filesRef * Create stable selectors using stateRef and filesRef
*/ */
export function createFileSelectors( export function createFileSelectors(
stateRef: React.RefObject<FileContextState>, stateRef: React.MutableRefObject<FileContextState>,
filesRef: React.RefObject<Map<FileId, File>> filesRef: React.MutableRefObject<Map<FileId, File>>
): FileContextSelectors { ): FileContextSelectors {
return { return {
getFile: (id: FileId) => { getFile: (id: FileId) => {
@ -125,8 +125,8 @@ export function buildQuickKeySetFromMetadata(metadata: { name: string; size: num
* Get primary file (first in list) - commonly used pattern * Get primary file (first in list) - commonly used pattern
*/ */
export function getPrimaryFile( export function getPrimaryFile(
stateRef: React.RefObject<FileContextState>, stateRef: React.MutableRefObject<FileContextState>,
filesRef: React.RefObject<Map<FileId, File>> filesRef: React.MutableRefObject<Map<FileId, File>>
): { file?: File; record?: StirlingFileStub } { ): { file?: File; record?: StirlingFileStub } {
const primaryFileId = stateRef.current.files.ids[0]; const primaryFileId = stateRef.current.files.ids[0];
if (!primaryFileId) return {}; if (!primaryFileId) return {};

View File

@ -16,7 +16,7 @@ export class FileLifecycleManager {
private fileGenerations = new Map<string, number>(); // Generation tokens to prevent stale cleanup private fileGenerations = new Map<string, number>(); // Generation tokens to prevent stale cleanup
constructor( constructor(
private filesRef: React.RefObject<Map<FileId, File>>, private filesRef: React.MutableRefObject<Map<FileId, File>>,
private dispatch: React.Dispatch<FileContextAction> private dispatch: React.Dispatch<FileContextAction>
) {} ) {}
@ -34,7 +34,7 @@ export class FileLifecycleManager {
/** /**
* Clean up resources for a specific file (with stateRef access for complete cleanup) * Clean up resources for a specific file (with stateRef access for complete cleanup)
*/ */
cleanupFile = (fileId: FileId, stateRef?: React.RefObject<any>): void => { cleanupFile = (fileId: FileId, stateRef?: React.MutableRefObject<any>): void => {
// Use comprehensive cleanup (same as removeFiles) // Use comprehensive cleanup (same as removeFiles)
this.cleanupAllResourcesForFile(fileId, stateRef); this.cleanupAllResourcesForFile(fileId, stateRef);
@ -68,7 +68,7 @@ export class FileLifecycleManager {
/** /**
* Schedule delayed cleanup for a file with generation token to prevent stale cleanup * Schedule delayed cleanup for a file with generation token to prevent stale cleanup
*/ */
scheduleCleanup = (fileId: FileId, delay: number = 30000, stateRef?: React.RefObject<any>): void => { scheduleCleanup = (fileId: FileId, delay: number = 30000, stateRef?: React.MutableRefObject<any>): void => {
// Cancel existing timer // Cancel existing timer
const existingTimer = this.cleanupTimers.get(fileId); const existingTimer = this.cleanupTimers.get(fileId);
if (existingTimer) { if (existingTimer) {
@ -101,7 +101,7 @@ export class FileLifecycleManager {
/** /**
* Remove a file immediately with complete resource cleanup * Remove a file immediately with complete resource cleanup
*/ */
removeFiles = (fileIds: FileId[], stateRef?: React.RefObject<any>): void => { removeFiles = (fileIds: FileId[], stateRef?: React.MutableRefObject<any>): void => {
fileIds.forEach(fileId => { fileIds.forEach(fileId => {
// Clean up all resources for this file // Clean up all resources for this file
this.cleanupAllResourcesForFile(fileId, stateRef); this.cleanupAllResourcesForFile(fileId, stateRef);
@ -114,7 +114,7 @@ export class FileLifecycleManager {
/** /**
* Complete resource cleanup for a single file * Complete resource cleanup for a single file
*/ */
private cleanupAllResourcesForFile = (fileId: FileId, stateRef?: React.RefObject<any>): void => { private cleanupAllResourcesForFile = (fileId: FileId, stateRef?: React.MutableRefObject<any>): void => {
// Remove from files ref // Remove from files ref
this.filesRef.current.delete(fileId); this.filesRef.current.delete(fileId);
@ -166,7 +166,7 @@ export class FileLifecycleManager {
/** /**
* Update file record with race condition guards * Update file record with race condition guards
*/ */
updateStirlingFileStub = (fileId: FileId, updates: Partial<StirlingFileStub>, stateRef?: React.RefObject<any>): void => { updateStirlingFileStub = (fileId: FileId, updates: Partial<StirlingFileStub>, stateRef?: React.MutableRefObject<any>): void => {
// Guard against updating removed files (race condition protection) // Guard against updating removed files (race condition protection)
if (!this.filesRef.current.has(fileId)) { if (!this.filesRef.current.has(fileId)) {
if (DEBUG) console.warn(`🗂️ Attempted to update removed file (filesRef): ${fileId}`); if (DEBUG) console.warn(`🗂️ Attempted to update removed file (filesRef): ${fileId}`);

View File

@ -32,7 +32,7 @@ import * as React from 'react';
*/ */
export const useIsOverflowing = (ref: React.RefObject<HTMLElement | null>, callback?: (isOverflow: boolean) => void) => { export const useIsOverflowing = (ref: React.MutableRefObject<HTMLElement | null>, callback?: (isOverflow: boolean) => void) => {
// State to track overflow status // State to track overflow status
const [isOverflow, setIsOverflow] = React.useState<boolean | undefined>(undefined); const [isOverflow, setIsOverflow] = React.useState<boolean | undefined>(undefined);

View File

@ -60,8 +60,8 @@ export function useTooltipPosition({
sidebarTooltip: boolean; sidebarTooltip: boolean;
position: Position; position: Position;
gap: number; gap: number;
triggerRef: React.RefObject<HTMLElement | null>; triggerRef: React.MutableRefObject<HTMLElement | null>;
tooltipRef: React.RefObject<HTMLDivElement | null>; tooltipRef: React.MutableRefObject<HTMLDivElement | null>;
sidebarRefs?: SidebarRefs; sidebarRefs?: SidebarRefs;
sidebarState?: SidebarState; sidebarState?: SidebarState;
}): PositionState { }): PositionState {

View File

@ -5,8 +5,8 @@ export interface SidebarState {
} }
export interface SidebarRefs { export interface SidebarRefs {
quickAccessRef: React.RefObject<HTMLDivElement | null>; quickAccessRef: React.MutableRefObject<HTMLDivElement | null>;
toolPanelRef: React.RefObject<HTMLDivElement | null>; toolPanelRef: React.MutableRefObject<HTMLDivElement | null>;
} }
export interface SidebarInfo { export interface SidebarInfo {