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.
LaTeX Generation: Template Substitution
The core mechanism generates LaTeX from a tree of PlacedBlock nodes. Here's the implementation:
function generateBlockLatex(block: PlacedBlock): string {
let latex = block.definition.latex;
for (let i = 0; i < block.definition.inputs; i++) {
const slotBlocks = block.children[i] || [];
let slotLatex: string;
if (slotBlocks.length > 0) {
slotLatex = slotBlocks.map(b => generateBlockLatex(b)).join(' ');
} else {
slotLatex = block.rawValues[i] || '\\square';
}
latex = latex.replace('□', slotLatex);
}
return latex;
}
Each block's definition.latex holds a template (e.g., \frac{□}{□}), and the placeholders □ are replaced sequentially with the results from child nodes. Because 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.
Challenge 1: Recursive Component Design
Problem: How do you represent a tree-structured UI with React components?
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 issues, I used a depth prop to dynamically adjust CSS scales and font sizes. Managing Drag & Drop across these nested boundaries was one of the trickiest parts of the project.
→ Next post: "Designing Recursive Components in React — Implementing Nested Math Structures"
Challenge 2: Immutable Tree Updates
Problem: How do you update a node at an arbitrary depth while preserving React's immutability?
When a user adds a block to a slot, we need to update a node at any depth in the tree. A recursive function called updateBlockInTree creates new objects only along the path to the target node, keeping all other references unchanged.
Undo/Redo relies on this immutability — it's implemented simply by storing the state history as an array.
→ Next post: Covered in detail in the recursive components article
Challenge 3: Keyboard Shortcuts and the Stale Closure Problem
Problem: Keyboard handlers inside useEffect can't read the latest state
When registering a handler with window.addEventListener('keydown', handler) inside useEffect, the state referenced in the handler gets trapped in the closure from the time it was registered (stale closure).
In BlockTeXu, I changed pendingAction (a pending state for two-step shortcuts) and selectedStockIndex (stock selection) from useState to useRef, so the handler can always read the latest values synchronously.
→ Next post: "Solving the Stale Closure Problem in Keyboard Handlers with useRef"
Challenge 4: Image Export with KaTeX × html-to-image
Problem: When converting KaTeX-rendered equations to images, the text doesn't appear
html-to-image uses SVG foreignObject to convert DOM to images, but KaTeX's web fonts (@font-face) aren't embedded correctly, resulting in blank white images.
I tried multiple approaches: waiting with document.fonts.ready, calling toPng twice (the first call primes the font cache), and considering a switch to html2canvas.
→ Next post: "The Web Font Problem When Exporting KaTeX Equations as Images"
Challenge 5: Block Operations with HTML5 Drag & Drop
Problem: How do you support both adding from a palette and reordering within the workspace?
The design stores block definition JSON in the dataTransfer of the HTML5 Drag & Drop API, which is then parsed at the drop target.
To distinguish between adding new blocks (palette → workspace) and reordering (within workspace), the data includes a { reorder: true, index } flag, and the drop handler branches accordingly. 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"
Challenge 6: Search Feature Design
Problem: How do you quickly find the right block among 216 options?
The implementation uses useMemo to control recalculation and separates category selection state from search state. During a search, all blocks across every category are included in the results. When the search is cleared, the display returns to category-based browsing.
Challenge 7: Vercel Deployment Pitfalls
Problem: It works locally, but the Vercel build fails
I ran into multiple obstacles before a successful deploy: Vite build failures caused by non-ASCII characters in Windows file paths (OneDrive/Desktop), and Linux file permission errors from committing node_modules to Git.
→ Next post: "Pitfalls When Deploying a Vite + React App to Vercel"
Challenge 8: SEO for a Single-Page Application
Problem: A React SPA's HTML is just <div id="root"></div>. Will search engines index it correctly?
I embedded meta descriptions, OGP tags, Twitter Cards, JSON-LD structured data, sitemap.xml, and robots.txt in the static HTML, then manually requested indexing via Google Search Console. Googlebot can execute JavaScript, so SPAs can be indexed — but including information in the initial HTML improves reliability.
→ Next post: "A Complete Guide to SEO for a Personal Dev Tool SPA"
Upcoming Posts
| # | 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, nested drops, bubbling control |
| 7 | Vercel deployment pitfalls | Non-ASCII path issues, node_modules permission errors, build commands |
| 8 | SEO for SPAs | Complete setup of meta/OGP/JSON-LD/Search Console |
Wrapping Up
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!

Top comments (0)