From d0342589acc9d4709e0a3fd2b0e14b055f1f05d4 Mon Sep 17 00:00:00 2001 From: tototomate123 Date: Sat, 7 Feb 2026 22:45:05 +0100 Subject: [PATCH] Speed up text editor for large inputs --- CHANGELOG.md | 1 + src/App.tsx | 33 +++++-------------- src/components/TextInputOverlay.tsx | 51 +++++++++++++++++++---------- src/components/modals/InfoModal.tsx | 15 --------- 4 files changed, 43 insertions(+), 57 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1df37af..01b54b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ - Split the big app file into smaller TypeScript modules and add `tsc` typechecking - Add PDF upload support (extract all text client-side for speed reading) - Improve text editor performance by applying changes explicitly instead of reprocessing on each keystroke +- Make the text editor use an uncontrolled textarea for better performance on large inputs ### 2026-02-02: 1.0.5 diff --git a/src/App.tsx b/src/App.tsx index 099976d..3baf60c 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -42,7 +42,6 @@ export default function App() { const positionsRef = useRef(savedSettings?.positions || {}); const [text, setText] = useState(() => savedSettings?.text || DEFAULT_TEXT); - const [draftText, setDraftText] = useState(() => savedSettings?.text || DEFAULT_TEXT); const [words, setWords] = useState(() => splitWords(savedSettings?.text || DEFAULT_TEXT), ); @@ -100,24 +99,15 @@ export default function App() { const timeoutRef = useRef(null); const prevTextRef = useRef(text); const fileInputRef = useRef(null); + const [textEditorSession, setTextEditorSession] = useState(0); const toggleTextInput = useCallback(() => { setShowTextInput((prev) => { const next = !prev; - if (next) setDraftText(text); + if (next) setTextEditorSession((s) => s + 1); return next; }); - }, [text]); - - const applyDraftText = useCallback(() => { - setText(draftText); - setShowTextInput(false); - }, [draftText]); - - const cancelDraftText = useCallback(() => { - setDraftText(text); - setShowTextInput(false); - }, [text]); + }, []); const togglePlay = useCallback(() => { if (currentIndex >= words.length - 1) { @@ -300,7 +290,6 @@ export default function App() { case "Escape": setShowInfo(false); setShowShortcuts(false); - setDraftText(text); setShowTextInput(false); setShowSettings(false); break; @@ -426,17 +415,13 @@ export default function App() { {showTextInput && ( setDraftText(e.target.value)} - onKeyDown={(e) => { - if (e.key === "Enter" && (e.ctrlKey || e.metaKey)) { - e.preventDefault(); - applyDraftText(); - } + key={textEditorSession} + initialText={text} + onApply={(nextText) => { + setText(nextText); + setShowTextInput(false); }} - isDirty={draftText !== text} - onApply={applyDraftText} - onCancel={cancelDraftText} + onCancel={() => setShowTextInput(false)} /> )} diff --git a/src/components/TextInputOverlay.tsx b/src/components/TextInputOverlay.tsx index 6e8480f..fcf425a 100644 --- a/src/components/TextInputOverlay.tsx +++ b/src/components/TextInputOverlay.tsx @@ -1,30 +1,45 @@ -import type { ChangeEventHandler, KeyboardEventHandler } from "react"; +import { useCallback, useRef, useState } from "react"; +import type { FormEventHandler, KeyboardEventHandler } from "react"; import { styles } from "../styles"; type Props = { - text: string; - onChangeText: ChangeEventHandler; - onKeyDown?: KeyboardEventHandler; - isDirty: boolean; - onApply: () => void; + initialText: string; + onApply: (nextText: string) => void; onCancel: () => void; }; -export function TextInputOverlay({ - text, - onChangeText, - onKeyDown, - isDirty, - onApply, - onCancel, -}: Props) { +export function TextInputOverlay({ initialText, onApply, onCancel }: Props) { + const textareaRef = useRef(null); + const [isDirty, setIsDirty] = useState(false); + + const handleInput = useCallback>(() => { + // Only flip once. Avoids re-rendering on every keystroke for huge texts. + setIsDirty((prev) => prev || true); + }, []); + + const apply = useCallback(() => { + const nextText = textareaRef.current?.value ?? initialText; + onApply(nextText); + }, [initialText, onApply]); + + const handleKeyDown = useCallback>( + (e) => { + if (e.key === "Enter" && (e.ctrlKey || e.metaKey)) { + e.preventDefault(); + apply(); + } + }, + [apply], + ); + return (