DEV Community

Cover image for Edit inside a .map(), a ternary, even a component in another file
Jack Lee
Jack Lee

Posted on • Originally published at blog.crossui.com

Edit inside a .map(), a ternary, even a component in another file

~7 min read · Tutorial

Here's the wall almost every visual editor hits. You can edit the outer JSX just fine. But the thing you actually need to change is a Card inside a .map(), and the tool goes quiet — because to render that Card, it needs to know what item is, and item only exists at runtime, inside the callback.

So you drop back to hand-editing. Which is fine, except the whole reason you opened a visual editor was to not do that.

CrossUI Studio's Layer Drill-down Engine is built for exactly this. You can isolate and edit any node at any depth — including the ones that live behind a runtime boundary. And drill-down doesn't stop at the file edge: Ctrl+click a child component and Studio follows it into the file where it's defined, keeping the canvas live the whole way down.

The depth problem, concretely

Take a list that every React app has some version of:

{items.map((item) => (
  <Card key={item.id} elevation={item.featured ? 3 : 1}>
    <CardContent>
      <Typography variant="h6">{item.title}</Typography>
      <Chip label={item.tag} size="small" />
    </CardContent>
  </Card>
))}
Enter fullscreen mode Exit fullscreen mode

You want to visually tweak that Card — bump the padding, change the Chip size, adjust the Typography variant. But a normal visual editor can only see the items.map(...) expression. It can't render the inside, because item is undefined until the loop runs. Same story for a ternary (cond ? <A/> : <B/>) and for children passed into a slot.

That runtime boundary is where most tools stop. Studio treats it as just another layer to step into.

Step 1 — Drill into the iteration

There are three ways to drill into a node, and they work whether the target is in the current file or in another one:

  • Ctrl+click on the canvas or in the component tree. Hover a drillable sub-component or expression and an action prompt appears; hold Ctrl to highlight the target with a mask, then click to step into it. Or just click the corresponding node in the left component tree.
  • JSX block selector / breadcrumb. Use the block selector or breadcrumb above the editor to see the hierarchy and jump to any level — handy when the tree is dense and you'd rather pick than click through.
  • Cursor focus (in-file). In the code editor, place your cursor inside a JSX block and Studio promotes it to the canvas focus. Editor and canvas point at the same AST, so moving in one moves the other.

Drill into the .map() and Studio renders an isolated canvas for the inner block — one iteration of your list, standing on its own.

Ctrl+click the .map() to drill into a single list item

Ctrl+click the .map() to drill into a single list item

In the shot above, Ctrl+click on the .map() drills into ProjectBoard['map-0-item'] — the first item of the list, isolated on the canvas while the full items.map(...) stays put in the code.

Supply the missing context: the Test Data Injector

An isolated list item has a problem: it lost the context its parent gave it. item is undefined now — there's no loop feeding it. So Studio gives you a Test Data Injector: a panel where you supply temporary mock data (JSON, strings, booleans) for any undefined props or variables at the current level. Fill in item and the isolated Card renders realistically, without the full app around it.

One thing that saves guesswork: hover any unassigned variable and the code editor highlights its exact location in the source, so you can see how item is used before you decide what to mock.

Now the Card is live. Not a screenshot — the actual component, rendered with real MUI theming.

Step 2 — Edit the inner node

With the inner block isolated, editing is the normal Studio loop. Click the Chip, change size from small to medium. Select the Card, promote its padding to a responsive { xs: 2, md: 3 }. Each change writes back into the callback body as a precise AST patch:

   {items.map((item) => (
-    <Card key={item.id} elevation={item.featured ? 3 : 1}>
+    <Card key={item.id} elevation={item.featured ? 3 : 1} sx={{ p: { xs: 2, md: 3 } }}>
       <CardContent>
         <Typography variant="h6">{item.title}</Typography>
-        <Chip label={item.tag} size="small" />
+        <Chip label={item.tag} size="medium" />
       </CardContent>
     </Card>
   ))}
Enter fullscreen mode Exit fullscreen mode

The .map() wrapper, the key, the ternary on elevation — all untouched. Your mock data never touches the file; it lived only in the drill-down session. What lands in the diff is exactly the two properties you changed.

The same mechanism works for conditional rendering (drill into the true or false side of a ternary, or a && branch, and edit it in isolation), for controlled sub-components that depend on external props or state, and for render-slot children. Any depth your tree actually has, you can reach.

Step 3 — Cross the file boundary

Real components don't live in one file — a page composes children that are imported from all over src/. A file-bound editor stops when it hits one of those imported children: you have to go find the file yourself, open it, and lose the canvas.

Studio doesn't stop there. Ctrl+click a child component and Studio drills straight into the file where it's defined, and keeps rendering it live on the canvas.

[Ctrl+click a child component to drill into the file where it's defined

[Ctrl+click a child component to drill into the file where it's defined

In the shot above, the active file is ShadcnDashboard.jsx. Its component tree lists the children it composes — KpiCard, RevenueChart, OrdersTable, Sidebar, Header, and so on. Sidebar isn't defined in this file; it's imported from ./components/Sidebar.jsx (line 16). Ctrl+click it, and Studio follows the import, opens the real definition, and drops you onto its canvas — the dashboard's sidebar, rendered live — with no manual file hunting and no losing your place. From there you can keep drilling: into that file's own children, into a .map() inside it, into a component it imports from somewhere else.

The same three drill-down methods from Step 1 apply here — Ctrl+click and the JSX block selector both cross file boundaries (cursor focus is the one that's in-file only, since it needs the code open in front of you). The effect is that the component tree stops being one-file-at-a-time. You navigate your UI the way it's actually structured — as a graph of components across files — while the canvas stays live at every hop.

Why it can do this

Same reason the rest of Studio works the way it does: everything is a view over the AST, not over a running app.

Drilling into a .map() is an AST operation — Studio finds the callback body node and renders it with an injected scope. Following a child component across files is also an AST operation — it resolves the import, parses the target file, locates the exported component, and renders that node. The runtime never has to succeed for any of this; Studio is reading structure, not observing execution. That's why it can isolate a node five levels deep, in a file you haven't opened, whether or not the full app renders.

If you want the architecture behind that, the dependency graph post covers how static AST parsing survives things a runtime tool can't.

Try it

The fastest way to feel this is on a real project with real nesting — a dashboard with a list of cards, a page that composes imported sections.

  1. Open a project (Playground, Git repo, or a local folder).
  2. Find a .map() or a ternary, drill in, use the Test Data Injector to mock the missing variable, and edit the inner node.
  3. Find an imported child component in the tree, Ctrl+click it, and watch Studio follow it into its own file.

Guest mode, no account: studio.crossui.com


About CrossUI Studio — A visual IDE for React & MUI. Code and canvas stay in two-way sync on the same AST: edit code and the canvas updates live; click an element on the canvas, edit its props visually, and the code changes with a surgical one-line diff. No build, no localhost — it runs in the browser, works on your real Git repo or a local folder, with no vendor lock-in.

Try it free → studio.crossui.com

Top comments (0)