npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

react-parent-owner-trees

v0.1.19

Published

Live parent-tree and owner-tree diagrams of a running React app, rendered as indented SVG trees with owner arcs, context areas, and boundary regions

Downloads

294

Readme

react-parent-owner-trees

Live diagrams of a running React app, in the notation from A Visual Notation for React's Parent and Owner Trees. The library draws two SVG trees:

  • Parent tree: where each component's output lands in the markup.
  • Owner tree: which component rendered which. An element's owner is the component whose JSX created it, so owner edges show where props come from.

None of this structure (which component owns which, where props come from, what a boundary scopes) lives in a single file, so reading it from code means reconstructing it in working memory. A picture can hold it for you. The post draws that picture by hand; this library draws it from your app.

Built on bippy for fiber access.

The demo app drawn as its two trees, side by side: the parent tree with dashed owner arcs sweeping back to DemoApp, and the flat owner tree next to it

The demo app as its two trees. In the parent tree, Stats and the Posts sit inside Frame's markup, but the dashed arcs show DemoApp owns them. The owner tree on the right makes that the structure: slot composition, exactly where the two trees diverge.

Live extraction needs a development build of React 19, because production builds strip owner information. The static walker (below) also works in production builds and at build time.

[!NOTE] Alpha. This is an early release. The API may change between 0.x versions, and live extraction has not been stress-tested on large apps yet. Issues and reports are welcome.

Install

Requires React 19 (the react peer range is >=19 <20). Live extraction additionally needs a development build of React; the static walker also works with production React.

npm install react-parent-owner-trees

bippy (the fiber-access layer) is a peer dependency that npm and pnpm install automatically. Only the /extract entry uses it, and the script tag below needs no install at all. With Yarn, add bippy yourself if you import /extract.

Quick start: script tag

The standalone build attaches a tree panel to any dev-mode React app. It is one self-contained file: bippy is bundled inside, so there is nothing to install. Load it as a classic script in <head>, before your app bundle:

<script src="https://unpkg.com/react-parent-owner-trees/dist/auto.global.js"></script>

(Or serve dist/auto.global.js from the package yourself.)

It adds a fixed panel with both trees, updated on every commit, plus a hide button and a badge to bring the panel back. On a large app, scope the view down: select a row, click scope to, and both trees narrow to that subtree. A breadcrumb in the header zooms back out. The script must load first so bippy can install the React DevTools hook before react-dom initializes.

Quick start: in your own page

The package has three entry points:

| Entry | What it gives you | Dependencies | | --- | --- | --- | | react-parent-owner-trees/core | createTreeRenderer and the InputNode type | none | | react-parent-owner-trees/extract | startExtraction: live fiber extraction, one tree per commit | bippy, react (dev build) | | react-parent-owner-trees/static | elementToTree: element walker, no DOM needed | react |

Wire extract to core like this:

// Import order matters: extract must be imported before react-dom,
// anywhere in your module graph. See "Import order" below.
import { startExtraction, contextName } from "react-parent-owner-trees/extract";
import { createTreeRenderer } from "react-parent-owner-trees/core";

const renderer = createTreeRenderer({ sizing: "fixed", contextName });

startExtraction((roots, diff, rendered) => {
  document.getElementById("parent")!.innerHTML = renderer.parentTreeSvg(
    roots,
    rendered,
  );
  document.getElementById("owner")!.innerHTML = renderer.ownerTreeSvg(
    roots,
    rendered,
  );
});

startExtraction calls you back on every React commit with an InputNode[], a TreeDiff against the previous snapshot, and the set of node ids that actually performed work in the commit (bailed-out reconciliation visits don't count). Pass that set to the SVG methods and the rows that rendered blink once, subtly: ambient liveness in the Observable-minimap sense. It's per-call data, not renderer state: pass it only on commit-triggered renders, so your own interaction re-renders don't replay the blink. The renderer turns it all into SVG strings; you put them in the DOM.

Page highlighting

The extract entry also ships the selection's second surface: while a row is focused, outline the DOM region its output governs in the running app. getNodeHostElements(id) resolves a row to the elements behind it (the nearest host descendants for a component, the node itself for a host row; empty for rows with nothing on the page), and createPageHighlighter() draws quiet fixed-position outlines that never affect page layout and track scroll and resize. Re-resolve after every render so commits that remount elements keep the outline honest:

import {
  createPageHighlighter,
  getNodeHostElements,
} from "react-parent-owner-trees/extract";

const highlighter = createPageHighlighter();

function render() {
  // ... innerHTML = renderer.parentTreeSvg(...) etc ...
  const selected = renderer.selectedId();
  highlighter.highlight(selected ? getNodeHostElements(selected) : []);
}

Import order

bippy installs the React DevTools hook as a side effect of being imported. React checks for that hook exactly once, when react-dom first evaluates. If the hook is not there yet, extraction stays silent forever. So the /extract entry (or the script tag) must load before anything that imports react-dom:

import "react-parent-owner-trees/extract"; // first
import { createRoot } from "react-dom/client"; // after

If the order is wrong, startExtraction() logs a console.error explaining the problem after a short grace period.

Static figures

elementToTree walks a React element tree without rendering it to the DOM. It calls your component functions with a mock hooks dispatcher, so it works at build time, in tests, and with production React:

import { elementToTree, contextName } from "react-parent-owner-trees/static";
import { createTreeRenderer } from "react-parent-owner-trees/core";

const roots = elementToTree(<App />);
const renderer = createTreeRenderer({ sizing: "content", contextName });
const svg = renderer.parentTreeSvg(roots);

Options: elementToTree(root, prefix, { rsc, suspended, errored }). rsc treats the tree as a server-component program (mark client entry points with markUseClient), suspended draws every Suspense boundary showing its fallback, and errored forces every error boundary into its caught state. Useful for drawing figures of states that are hard to catch live.

Renderer options

createTreeRenderer(options) returns a renderer instance that owns all interactive state (selection, folds, active areas), so several diagrams on one page never share toggles.

| Option | Default | Meaning | | --- | --- | --- | | sizing | "content" | "content" sizes the SVG to the widest row (figures); "fixed" keeps a stable width (live tool, where rows churn every commit) | | select | on | click a node to focus its neighborhood; click again or press Escape to clear | | collapse | off | disclosure triangles and double-click subtree collapse | | hooks | off | per-component hook strips, one glyph per hook call | | serverRegions | off | tinted regions behind server-component rows | | sourceNav | off | data-node attributes and a cmd-click source hint | | serverColor | a teal (hsl(191 65% 40%)) | the "came from the server" color | | contextName | — | resolves a context id to a display name; pass the contextName export of whichever producer you use |

Dark mode

The diagrams render correctly on any page out of the box. The four neutral colors (background, three text weights) read from CSS custom properties with light-mode fallbacks, so a dark page can flip them:

:root {
  --rpot-bg: #1a1a1a;
  --rpot-text: #ededed;
  --rpot-text-light: #b5b5b5;
  --rpot-text-lighter: #757575;
}

This is deliberately the whole theming surface. The accent colors (state dots, memo discs, context palette, boundary colors) are part of the notation and stay fixed.

Interaction

The renderer returns plain SVG strings and never touches the DOM. Interactivity is wired by you: listen for clicks on your container, find the closest element with one of these data attributes, call the matching renderer method, then re-render both trees.

| Data attribute | Renderer method | | --- | --- | | data-select-toggle | toggleSelect(id) | | data-fold-toggle | toggleFold(id) | | data-collapse-toggle | toggleCollapse(id) | | data-context-toggle | toggleContext(id) | | data-boundary-toggle | toggleBoundary(id) | | data-memo-toggle | toggleMemo(id) | | data-hooks-toggle | toggleHooks(id) |

There are also direct setters: setArcStyle, setHooksMode, setMarks, setView (hide DOM rows or structural rows), clearSelect, and set*Active variants of the toggles. See the TreeRenderer type for the full interface.

The core entry also exports small helpers for working with trees outside the renderer: scopeToSubtree (narrow the view to one node's subtree, so a large app can render just the region you're working on instead of the whole tree), applyView/hideDom/hideStructural (filter a tree the same way setView does), diffSnapshots (the TreeDiff between two snapshots), and serializeTree. Apply them between the extraction callback and the SVG methods:

startExtraction((roots, diff, rendered) => {
  const view = scopeToSubtree(roots, scopedId);
  parentEl.innerHTML = renderer.parentTreeSvg(view, rendered);
  ownerEl.innerHTML = renderer.ownerTreeSvg(view, rendered);
});

If the scoped id disappears (the component unmounted), the scope dissolves to the full tree instead of blanking the panel.

What the diagrams show

Every mark in the diagrams (circles, owner arcs, state dots, memo discs, context areas, boundary squares, server rows, folds) is summarized in the notation overview, with links into the full walkthrough post for each one.

Known limitations

  • Live extraction works only with development builds of React 19.
  • If a selected node's owner sits inside a folded run, that owner arc is not drawn (unfolding on selection is planned).

Development

npm install
npm run dev     # demo app + live tool
npm test        # renderer behavior + unit tests
npm run build:lib && npm run build:auto

The library lives in src/lib/; src/main.tsx and src/demo/ are the dev app the tool observes. Design decisions are recorded in docs/adr/, the domain vocabulary in CONTEXT.md.

License

MIT