8 Challenges I Faced Building a Complex Visual Editor in React
Source: Dev.to
Overview
Editing LaTeX as raw strings is a nightmare for beginners. That’s why I built BlockTeXu — a block‑based visual LaTeX equation editor where you snap together math symbols like LEGO blocks, and it generates valid LaTeX automatically.
Along the way, I ran into a number of challenges that are broadly useful in React development: recursive components, immutable tree operations, Drag & Drop, and more. This article gives an overview of the 8 technical challenges I faced and how I approached them. I’ll dive deeper into each topic in upcoming posts.
Core LaTeX Generation
The core mechanism generates LaTeX from a tree of PlacedBlock nodes.
function generateBlockLatex(block: PlacedBlock): string {
let latex = block.definition.latex;
for (let i = 0; i 0) {
slotLatex = slotBlocks.map(b => generateBlockLatex(b)).join(' ');
} else {
slotLatex = block.rawValues[i] || '\\square';
}
latex = latex.replace('□', slotLatex);
}
return latex;
}
- Each block’s
definition.latexholds a template (e.g.\frac{□}{□}), and the placeholder□is replaced sequentially with the results from child nodes. - Because the templates guarantee correct syntax, this approach structurally prevents LaTeX syntax errors.
The mechanism is simple, but implementing the actual UI, interactions, and output revealed several React‑specific challenges.
Technical Challenges
Problem 1 – Representing a Tree‑Structured UI with React
In BlockTeXu the UI is essentially a visual representation of an Abstract Syntax Tree (AST). I implemented this using a recursive pattern:
PlacedBlock → BlockSlot → PlacedBlock
PlacedBlock (Fraction)
├── BlockSlot[0] (Numerator)
│ └── PlacedBlock (Integral)
│ ├── BlockSlot[0] (Lower bound)
│ └── BlockSlot[1] (Upper bound)
└── BlockSlot[1] (Denominator)
└── PlacedBlock (Variable y)
- Key Insight: To prevent infinite UI scaling, I used a
depthprop to dynamically adjust CSS scales and font sizes. - Managing Drag & Drop across these nested boundaries was one of the trickiest parts.
→ Next post: “Designing Recursive Components in React — Implementing Nested Math Structures”
Problem 2 – Updating a Node at Arbitrary Depth While Preserving Immutability
When a user adds a block to a slot, we must update a node at any depth in the tree. I wrote a recursive function updateBlockInTree that creates new objects only along the path to the target node, leaving all other references unchanged.
- This immutability enables a simple undo/redo implementation by storing the state history as an array.
→ Next post: Covered in detail in the recursive components article
Problem 3 – Stale Closures in Keyboard Handlers
Registering a handler with window.addEventListener('keydown', handler) inside useEffect traps the state in the closure from the time it was registered.
Solution in BlockTeXu:
- Convert the stale state (
pendingAction,selectedStockIndex) fromuseStatetouseRef. - The handler now reads the latest values synchronously.
→ Next post: “Solving the Stale Closure Problem in Keyboard Handlers with useRef”
Problem 4 – KaTeX‑Rendered Equations Exported as Images Lose Text
html-to-image uses an SVG foreignObject to convert the DOM to an image, but KaTeX’s web fonts (@font-face) aren’t embedded correctly, resulting in blank images.
Attempts:
- Wait for
document.fonts.ready. - Call
toPngtwice (the first call primes the font cache). - Consider switching to
html2canvas.
→ Next post: “The Web Font Problem When Exporting KaTeX Equations as Images”
Problem 5 – Supporting Both Palette‑Add and Workspace‑Reorder Drag & Drop
- The design stores the block definition JSON in the
dataTransferof the HTML5 Drag & Drop API. - To differentiate adding new blocks (palette → workspace) from reordering (within workspace), the transferred data includes a
{ reorder: true, index }flag. - Drops into nested slots are also supported, making event bubbling control (
stopPropagation) essential.
→ Next post: “Design Patterns for Building a Block Editor with HTML5 Drag & Drop”
Problem 6 – Quickly Finding the Right Block Among 216 Options
- Use
useMemoto control recalculation. - Separate category selection state from search state.
- During a search, all blocks across every category are included; clearing the search returns to category‑based browsing.
Problem 7 – Vercel Build Fails Even Though It Works Locally
- Vite build failures caused by non‑ASCII characters in Windows file paths (e.g.,
OneDrive/Desktop). - Linux file‑permission errors from committing
node_modulesto Git.
→ Next post: “Pitfalls When Deploying a Vite + React App to Vercel”
Problem 8 – SEO for a React SPA
- Embedded meta descriptions, OGP tags, Twitter Cards, JSON‑LD structured data,
sitemap.xml, androbots.txtin the static HTML. - Manually requested indexing via Google Search Console.
- Googlebot can execute JavaScript, so SPAs can be indexed, but providing information in the initial HTML improves reliability.
→ Next post: “A Complete Guide to SEO for a Personal Dev Tool SPA”
Quick Reference Table
| # | Topic | Summary |
|---|---|---|
| 3 | Recursive component design | PlacedBlock → BlockSlot recursion, depth management, immutable tree updates |
| 4 | Stale closure in keyboard handlers | useRef synchronization pattern, pendingAction design |
| 5 | KaTeX image export font issues | How html-to-image works, double rendering, html2canvas comparison |
| 6 | Block editor with Drag & Drop | dataTransfer design |
Stay tuned for the deeper dives!
7. Vercel deployment pitfalls
- Non‑ASCII path issues
node_modulespermission errors- Build commands
8. SEO for SPAs
- Complete setup of meta / OGP / JSON‑LD / Search Console
- Nested drops, bubbling control
Recursive components, immutable tree operations, Drag & Drop, web‑font image rendering — building a single editor touched on a surprisingly wide range of React topics.
I’ll be sharing implementation code and lessons learned for each topic in upcoming posts.
If you’re interested in recursive UI or tree‑operation design, I’d love to hear your thoughts in the comments. And if you’d like to try BlockTeXu yourself, check it out at blocktexu.com/en.