Editing React components that never rendered

linb1 pts0 comments

Editing React components that never rendered - CrossUI Studio — Symmetric Visual IDE

Author

User<br>linb<br>Posts by this author<br>Posts by this author

~7 min read · Engineering

There are only two ways a tool can learn the shape of your React code. Which one you pick decides the single most important thing about the tool: whether it's available in the exact moment you need it most.

Two ways to know a codebase

Runtime introspection watches a running app. React DevTools, profilers, error overlays — they read the live fiber tree after the app mounts. This is the richer of the two: they know real prop values, real state, what actually rendered. But all of that is conditional on one thing — the app has to successfully run first. The moment a render throws, they go dark. Nothing mounted, so there's nothing to inspect. And a module that failed to initialize is invisible to them by definition.

Static AST analysis reads source. It parses each file into an abstract syntax tree and reads the structure directly — every JSX node, every import, every prop — without executing anything. It's poorer in one sense: it can't tell you what a variable held at 2:04pm, because nothing ran. But it has a property no runtime tool can match: it works on code that has never once rendered cleanly.

Most developer tooling is built on the first model. We built CrossUI Studio — a visual editor for React — on the second, on purpose. This post is about why that constraint turned out to be the whole point.

The moment tooling abandons you

Here's the scenario that shaped the decision. A component renders blank. The stack trace points at the component, but the real failure is five imports down — a dynamic import that didn't resolve, a peer dependency that got bumped, a circular reference that's undefined on first access. The component is innocent; the thing it transitively pulls in is the culprit.

This is exactly when you most need to understand your code's structure — which file imports what, where the break is, what the surrounding code looks like. And it's exactly when every runtime tool has gone dark, because the app didn't render. DevTools shows nothing. The profiler has no render to profile. The error overlay gives you a line number but not the shape of the code around it. They all need the app to run first, and the entire problem is that it won't.

The one moment you most need to see your code's structure is the one moment a runtime tool refuses to produce it.

A static AST tool has no such dependency. It parsed the files when you opened them; it can draw the whole import graph, point at the broken module, and show you the code around it — with the app fully crashed. A broken page isn't a dead end. It's the entry point.

Editing what you can't run

The same property unlocks something stranger: editing a component that isn't rendering, in a file you never opened.

Think about what a visual editor normally needs to render a that lives inside a .map(). To put that Card on a canvas, it has to know what item is — and item only exists at runtime, inside the loop, when real data flows through. A runtime-based editor simply can't render that Card in isolation; there's no live item to feed it. So most visual editors quietly refuse to go inside a .map() at all.

Working from the AST, the problem reframes. We don't need a running loop; we need the node. Parse the file, find the JSX inside the map callback, and render that subtree in isolation. The one thing missing is the value of item — so we ask you for it (a small mock in a panel) rather than requiring the app to produce it. The Card renders, live, with real theming, standing on its own.

And because the unit of understanding is the AST node rather than the mounted component, drill-down doesn't stop at the file boundary. A child component imported from another file is just another edge in the graph. Follow the import, parse that file, render its node — you're editing a component defined somewhere you never opened, in an app that may never have rendered as a whole.

The cost of the constraint

None of this is free, and pretending otherwise would be dishonest. Static analysis buys unconditional availability by giving up runtime knowledge:

Fully dynamic imports are unresolvable. import('./pages/' + name) with a runtime-computed path can't be resolved statically. We mark the edge rather than guess.

We see structure, not values. The AST tells you AIPanel reaches TreeSitter; it can't tell you what the parser returned on a given render. Mock data stands in for real data; it doesn't replay it.

Exotic resolution needs config. Non-standard bundler aliases or monorepo path magic resolve best when we can read your tsconfig/jsconfig. Without it, some edges fall back to raw specifiers.

These are real boundaries. But notice none of them touch the core promise: the tool is there when you're stuck, because it never needed your code to run in the first place.

Why this is one idea, not many

The...

code runtime render never tool real

Related Articles