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-conditional-hooks

v0.0.1

Published

Run conditional hooks in React (experiment)

Readme

react-conditional-hooks

version downloads

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

this project uses react internals, which can change at any time. we don't recommend depending 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.

Run conditional hooks in React (experiment)

Install

Install the package with your project’s package manager:

npm install react-conditional-hooks

Use hooks inside a conditional branch

This app puts useState and useMemo inside a conditional branch:

import { installConditionalHooks } from "react-conditional-hooks";
import { useMemo, useState, type ReactNode } from "react";

installConditionalHooks();

export const App = (): ReactNode => {
  const [isPartyMode, setIsPartyMode] = useState(false);

  if (!isPartyMode) {
    return <button onClick={() => setIsPartyMode(true)}>Start party</button>;
  }

  const [ducks, setDucks] = useState(3);
  const danceFloor = useMemo(() => "🦆".repeat(ducks), [ducks]);
  return (
    <section>
      <p>{danceFloor}</p>
      <button onClick={() => setDucks(ducks + 1)}>Add duck</button>
      <button onClick={() => setIsPartyMode(false)}>Stop party</button>
    </section>
  );
};

Start the party, add a duck, stop it, and start it again. The conditional ducks state resumes at its previous value. React normally rejects this pattern because the number of hooks changes between renders.

The runtime supports:

  • State: useState, useReducer, and useRef
  • Memoization: useMemo and useCallback
  • Effects: useEffect and useLayoutEffect

useContext continues through React’s dispatcher because context lookup belongs to the renderer.

How conditional hooks work

The runtime intercepts React’s dispatcher, derives an identity from each hook’s source location, and stores state beside the component Fiber. Bippy supplies renderer access and commit lifecycle events.

React sends hook calls through a dispatcher

Functions such as useState delegate to React’s current dispatcher. The runtime replaces the dispatcher property with a proxy and redirects supported hooks:

const proxy = new Proxy(dispatcher, {
  get: (target, property, receiver) => {
    if (property === "useState") {
      return (initialState: unknown) =>
        readStateCell(getAutomaticHookKey(runtime, "useState"), initialState);
    }

    return Reflect.get(target, property, receiver);
  },
});

Application code still calls React’s APIs. Unsupported hooks pass through to the original dispatcher.

The source callsite identifies each hook

React normally identifies a hook by its position in the component’s hook list. This runtime captures an error stack and uses Bippy to parse V8, Firefox, and Safari stack formats:

const applicationFrame = parseStack(stack).find(
  (stackFrame) =>
    Boolean(stackFrame.fileName) &&
    !isRuntimeStackFrame(stackFrame) &&
    !isReactStackFrame(stackFrame),
);

if (!applicationFrame?.fileName) throw new Error("Missing callsite");

const callsiteKey = [
  applicationFrame.fileName,
  applicationFrame.lineNumber,
  applicationFrame.columnNumber,
].join(":");

The file, line, and column form the stable identity. A per-render occurrence counter separates repeated calls from the same callsite.

Each Fiber owns a committed scope

React creates a Fiber for each mounted component instance. The runtime associates that Fiber with a side table instead of modifying React’s private hook list:

interface ConditionalHookScope {
  cells: Map<PropertyKey, ConditionalHookCell>;
  effects: Map<PropertyKey, ConditionalEffectCell>;
  fiber: Fiber;
}

const scopeByFiber = new WeakMap<Fiber, ConditionalHookScope>();
const renderFrameByFiber = new WeakMap<Fiber, ConditionalRenderFrame>();

Hook calls write into a temporary render frame. A successful commit promotes that frame into the Fiber’s scope. Suspended, failed, or abandoned renders never change committed state.

Commits control effect cleanup

Each successful render records the effects whose branches ran. The commit removes effects that disappeared from the next render:

for (const [key, effectCell] of scope.effects) {
  if (nextEffects.has(key)) continue;

  runEffectCleanup(effectCell);
  scope.effects.delete(key);
}

State cells remain in the scope when their branch disappears. Effects clean up when the branch exits and start again when it returns.

Development

Run the package checks from the repository root:

ni
nr build
nr test
nr check

License

MIT