SkillAgentSearch skills...

Bippy

⚠️ hack into react internals

Install / Use

/learn @aidenybai/Bippy
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

[!WARNING] ⚠️⚠️⚠️ this project may break production apps and cause unexpected behavior ⚠️⚠️⚠️

this project uses react internals, which can change at any time. it is not recommended to depend on internals unless you really, really have to. by proceeding, you acknowledge the risk of breaking your own code or apps that use your code.

<img src="https://github.com/aidenybai/bippy/blob/main/.github/public/bippy.png?raw=true" width="60" align="center" /> bippy

version downloads

bippy is a toolkit to hack into react internals

by default, you cannot access react internals. bippy bypasses this by "pretending" to be react devtools, giving you access to the fiber tree and other internals.

  • works outside of react – no react code modification needed
  • utility functions that work across modern react (v17-19)
  • no prior react source code knowledge required
import { onCommitFiberRoot, traverseFiber } from "bippy"; // must be imported BEFORE react

instrument({
  onCommitFiberRoot: (root) => {
    traverseFiber(root.current, (fiber) => {
      // prints every fiber in the current React tree
      console.log("fiber:", fiber);
    });
  },
});

how it works & motivation

bippy allows you to access and use react fibers outside of react components.

a react fiber is a "unit of execution." this means react will do something based on the data in a fiber. each fiber either represents a composite (function/class component) or a host (dom element).

here is a live visualization of what the fiber tree looks like, and here is a deep dive article.

fibers are useful because they contain information about the react app (component props, state, contexts, etc.). a simplified version of a fiber looks roughly like this:

interface Fiber {
  // component type (function/class)
  type: any;

  child: Fiber | null;
  sibling: Fiber | null;

  // stateNode is the host fiber (e.g. DOM element)
  stateNode: Node | null;

  // parent fiber
  return: Fiber | null;

  // the previous or current version of the fiber
  alternate: Fiber | null;

  // saved props input
  memoizedProps: any;

  // state (useState, useReducer, useSES, etc.)
  memoizedState: any;

  // contexts (useContext)
  dependencies: Dependencies | null;

  // effects (useEffect, useLayoutEffect, etc.)
  updateQueue: any;
}

here, the child, sibling, and return properties are pointers to other fibers in the tree.

additionally, memoizedProps, memoizedState, and dependencies are the fiber's props, state, and contexts.

while all of the information is there, it's not super easy to work with, and changes frequently across different versions of react. bippy simplifies this by providing utility functions like:

  • traverseRenderedFibers to detect renders and traverseFiber to traverse the overall fiber tree
    • (instead of child, sibling, and return pointers)
  • traverseProps, traverseState, and traverseContexts to traverse the fiber's props, state, and contexts
    • (instead of memoizedProps, memoizedState, and dependencies)

however, fibers aren't directly accessible by the user. so, we have to hack our way around to accessing it.

luckily, react reads from a property in the window object: window.__REACT_DEVTOOLS_GLOBAL_HOOK__ and runs handlers on it when certain events happen. this property must exist before react's bundle is executed. this is intended for react devtools, but we can use it to our advantage.

here's what it roughly looks like:

interface __REACT_DEVTOOLS_GLOBAL_HOOK__ {
  // list of renderers (react-dom, react-native, etc.)
  renderers: Map<RendererID, reactRenderer>;

  // called when react has rendered everything for an update and the fiber tree is fully built and ready to
  // apply changes to the host tree (e.g. DOM mutations)
  onCommitFiberRoot: (rendererID: RendererID, root: FiberRoot, commitPriority?: number) => void;

  // called when effects run
  onPostCommitFiberRoot: (rendererID: RendererID, root: FiberRoot) => void;

  // called when a specific fiber unmounts
  onCommitFiberUnmount: (rendererID: RendererID, fiber: Fiber) => void;
}

bippy works by monkey-patching window.__REACT_DEVTOOLS_GLOBAL_HOOK__ with our own custom handlers. bippy simplifies this by providing utility functions like:

  • instrument to safely patch window.__REACT_DEVTOOLS_GLOBAL_HOOK__
    • (instead of directly mutating onCommitFiberRoot, ...)
  • secure to wrap your handlers in a try/catch and determine if handlers are safe to run
    • (instead of rawdogging window.__REACT_DEVTOOLS_GLOBAL_HOOK__ handlers, which may crash your app)
  • traverseRenderedFibers to traverse the fiber tree and determine which fibers have actually rendered
    • (instead of child, sibling, and return pointers)
  • traverseFiber to traverse the fiber tree, regardless of whether it has rendered
    • (instead of child, sibling, and return pointers)
  • setFiberId / getFiberId to set and get a fiber's id
    • (instead of anonymous fibers with no identity)

how to use

we recommend installing via npm.

this package should be imported before a React app runs. this will add a special object to the global which is used by React for providing its internals to the tool for analysis (React Devtools does the same). as soon as React library is loaded and attached to the tool, bippy starts collecting data about what is going on in React's internals.

npm install bippy

since bippy needs to be imported before react, some bundlers require specific configuration to ensure the correct import order.

next.js

in next.js 15.3+, use the instrumentation-client.js file to ensure bippy loads before react. create this file at the root of your application (or inside the src folder if you're using the src directory structure):

// instrumentation-client.ts
import "bippy";

this file executes before react hydration, making it the ideal place to initialize bippy.

vite

in vite, import bippy at the very top of your main entry point (typically src/main.tsx or src/main.ts) before any react imports:

// src/main.tsx
import "bippy";
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";

// ... rest of your code

the import order is critical: bippy must be imported before any react packages.

note for library maintainers: if you're building a library and want to define your own utility functions while minimizing bundle size, you can use bippy/install-hook-only (~90 bytes) instead of the main bippy export. this only installs the react devtools hook without importing any utility functions, allowing you to import only what you need from bippy/core or define your own fiber utilities. that said, the full bippy package is only ~4kb gzipped, so bundle size is rarely a concern.

import "bippy/install-hook-only"; // only installs the hook
import { getRDTHook, traverseFiber } from "bippy/core"; // import only what you need
import * as React from "react"; // import react AFTER the hook is installed

const hook = getRDTHook();
// define your own utilities or use only specific ones

API reference

instrument

patches window.__REACT_DEVTOOLS_GLOBAL_HOOK__ with your handlers. must be imported before react, and must be initialized to properly run any other methods.

use with the secure function to prevent uncaught errors from crashing your app.

import { instrument, secure } from "bippy"; // must be imported BEFORE react
import * as React from "react";

instrument(
  secure({
    onCommitFiberRoot(rendererID, root) {
      console.log("root ready to commit", root);
    },
    onPostCommitFiberRoot(rendererID, root) {
      console.log("root with effects committed", root);
    },
    onCommitFiberUnmount(rendererID, fiber) {
      console.log("fiber unmounted", fiber);
    },
  }),
);

getRDTHook

returns the window.__REACT_DEVTOOLS_GLOBAL_HOOK__ object. great for advanced use cases, such as accessing or modifying the renderers property.

import { getRDTHook } from "bippy";

const hook = getRDTHook();
console.log(hook);

traverseRenderedFibers

not every fiber in the fiber tree renders. traverseRenderedFibers allows you to traverse the fiber tree and determine which fibers have actually rendered.

import { instrument, secure, traverseRenderedFibers } from "bippy"; // must be imported BEFORE react
import * as React from "react";

instrument(
  secure({
    onCommitFiberRoot(rendererID, root) {
      traverseRenderedFibers(root, (fiber) => {
        console.log("fiber rendered", fiber);
      });
    },
  }),
);

traverseFiber

calls a callback on every fiber in the fiber tree.

import { instrument, secure, traverseFiber } from "bippy"; // must be imported BEFORE react
import * as React from "react";

instrument(
  secure({
    onCommitFiberRoot(rendererID, root) {
      traverseFiber(root.current, (fiber) => {
        console.log(fiber);
      });
    },
  }),
);

traverseProps

traverses the props of a fiber.

import { traverseProps } from "bippy";

// ...

traverseProps(fiber, (propName, next, prev) => {
  console.log(propName, next, prev);
});

traverseState

tra

View on GitHub
GitHub Stars1.3k
CategoryDevelopment
Updated10h ago
Forks36

Languages

TypeScript

Security Score

100/100

Audited on Apr 5, 2026

No findings