main 3df0b16ff1fd cached
26 files
101.7 KB
25.9k tokens
32 symbols
1 requests
Download .txt
Repository: brandonmcconnell/render-hooks
Branch: main
Commit: 3df0b16ff1fd
Files: 26
Total size: 101.7 KB

Directory structure:
gitextract_ii3d9lst/

├── .github/
│   └── workflows/
│       └── chromatic.yml
├── .gitignore
├── .npmignore
├── .storybook/
│   ├── Theme.js
│   ├── main.ts
│   ├── manager.ts
│   ├── preview.ts
│   └── storybook.css
├── LICENSE
├── README.md
├── package.json
├── src/
│   ├── index.test.tsx
│   ├── index.tsx
│   └── stories/
│       ├── 00-QuickStart.stories.tsx
│       ├── 01-BuiltInHooks.stories.tsx
│       ├── 02-CustomHooks.stories.tsx
│       ├── 03-NestingRenderHooks.stories.tsx
│       ├── Header.tsx
│       ├── Page.tsx
│       ├── assets/
│       │   └── avif-test-image.avif
│       ├── button.css
│       ├── header.css
│       └── page.css
├── tsconfig.json
├── vite-env.d.ts
└── vitest.config.ts

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/workflows/chromatic.yml
================================================
name: "Chromatic"

on: push

jobs:
  chromatic:
    name: Run Chromatic
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - uses: actions/setup-node@v4
        with:
          node-version: 22.12.0
      - name: Install dependencies
        # ⚠️ See your package manager's documentation for the correct command to install dependencies in a CI environment.
        run: npm ci
      - name: Run Chromatic
        uses: chromaui/action@latest
        env:
          # Expose the secret so Vite/Storybook can replace `import.meta.env.STORYBOOK_CODESANDBOX_TOKEN`
          STORYBOOK_CODESANDBOX_TOKEN: ${{ secrets.STORYBOOK_CODESANDBOX_TOKEN }}
        with:
          # ⚠️ Make sure to configure a `CHROMATIC_PROJECT_TOKEN` repository secret
          projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
          buildScriptName: build-storybook

================================================
FILE: .gitignore
================================================
# Dependencies
node_modules/

# Build output
dist/

# Logs
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*

# Misc
.DS_Store
.env
.env.local
.env.development.local
.env.test.local
.env.production.local

# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json

# Runtime data
pids
*.pid
*.seed
*.pid.lock

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

# Coverage directory used by tools like istanbul
coverage
*.lcov

# nyc test coverage
.nyc_output

# Vitest cache and coverage
.vitest_cache/
coverage/

# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt

# Bower dependency directory (https://bower.io/)
bower_components

# node-waf configuration
.lock-wscript

# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release

# Dependency directories
jspm_packages/

# Snowpack dependency directory (https://snowpack.dev/)
web_modules/

# TypeScript cache
*.tsbuildinfo

# Optional npm cache directory
.npm

# Optional eslint cache
.eslintcache

# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/

# Optional REPL history
.node_repl_history

# Output of 'npm pack'
*.tgz

# Yarn Integrity file
.yarnclean

# parcel-bundler cache files
.cache
.parcel-cache

# Next.js build output
.next
out

# Nuxt.js build output
.nuxt

# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#unzipping-automatic-routing-in-pages-now-supports-dynamic-routing
# public

# vuepress build output
.vuepress/dist

# Docusaurus build output
.docusaurus

# Serverless directories
.serverless/

# FuseBox cache
.fusebox/

# DynamoDB Local files
.dynamodb/

# TernJS port file
.tern-port

# Stores VSCode versions used for testing VSCode extensions
.vscode-test

# Gatsby Supabase cache files
.gatsby-supabase-cache 
*storybook.log
storybook-static/


================================================
FILE: .npmignore
================================================
# Source files
src/

# Config files
.eslintignore
.eslintrc.js
.prettierrc.js
.prettierignore

# Tests
__tests__/
src/**/*.test.ts
src/**/*.test.tsx
vitest.config.ts
vitest.setup.ts
coverage/

# Storybook
.storybook/
stories/
storybook-static/

# Other
.DS_Store
*.log

# Git
.git/
.gitignore
.env

# Other 

================================================
FILE: .storybook/Theme.js
================================================
import { create } from '@storybook/theming';
 
export default create({
  base: 'dark',
  brandTitle: 'RenderHooks',
  brandUrl: 'https://github.com/brandonmcconnell/render-hooks',
  brandImage: 'https://github.com/brandonmcconnell/render-hooks/blob/main/.github/render-hooks-logo_full.png?raw=true',
  // brandTarget: '_self',
});

================================================
FILE: .storybook/main.ts
================================================
import type { StorybookConfig } from '@storybook/react-vite';

const config: StorybookConfig = {
  "stories": [
    "../src/**/*.mdx",
    "../src/**/*.stories.@(js|jsx|mjs|ts|tsx)"
  ],
  "addons": [
    "@storybook/addon-essentials",
    "@chromatic-com/storybook",
    "@codesandbox/storybook-addon"
  ],
  "framework": {
    "name": "@storybook/react-vite",
    "options": {}
  },
  "typescript": {
    "reactDocgen": "react-docgen-typescript",
    "reactDocgenTypescriptOptions": {
      // You might need to specify compilerOptions here if they differ from your main tsconfig
      // For example, to ensure JSX is handled correctly:
      // compilerOptions: {
      //   jsx: "react-jsx", // or "react"
      // },
      // Filter props from node_modules (optional, but good practice)
      propFilter: (prop) => (prop.parent ? !/node_modules/.test(prop.parent.fileName) : true),
      // If your components are not directly in `src` or you have a specific tsconfig for app compilation:
      // tsconfigPath: "./tsconfig.app.json", // Or your relevant tsconfig
    }
  }
};
export default config;

================================================
FILE: .storybook/manager.ts
================================================
import { addons } from '@storybook/manager-api';
import theme from './Theme';
import './storybook.css';
 
addons.setConfig({
  theme,
});

================================================
FILE: .storybook/preview.ts
================================================
import type { Preview } from '@storybook/react'
import theme from './Theme'
import './storybook.css';

const preview: Preview = {
  parameters: {
    docs: {
      theme,
    },
    codesandbox: {
      /**
       * @required
       * Workspace API key from codesandbox.io/t/permissions.
       * This sandbox is created inside the given workspace
       * and can be shared with team members.
       */
      // @ts-expect-error (2339) Property 'env' does not exist on type 'ImportMeta'.
      apiToken: import.meta.env.STORYBOOK_CODESANDBOX_TOKEN,
 
      /**
       * @required
       * Dependencies list to be installed in the sandbox.
       *
       * @note You cannot use local modules or packages since
       * this story runs in an isolated environment (sandbox)
       * inside CodeSandbox. As such, the sandbox doesn't have
       * access to your file system.
       *
       * Example:
       */
      dependencies: {
        "render-hooks": "latest",
        "react": "latest",
        "react-dom": "latest",
      },
 
      /**
       * @required
       * CodeSandbox will try to import all components by default from
       * the given package, in case `mapComponent` property is not provided.
       *
       * This property is useful when your components imports are predictable
       * and come from a single package and entry point.
       */
      fallbackImport: "render-hooks",
    },
    controls: {
      matchers: {
       color: /(background|color)$/i,
       date: /Date$/i,
      },
    },
  },
};

export default preview;

================================================
FILE: .storybook/storybook.css
================================================
@import url('https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap');

body {
  font-family: "Inter", sans-serif;
  font-optical-sizing: auto;
  font-weight: 400;
  font-style: normal;
}

.sidebar-header a[title] img[class] {
  width: 220px !important;
  max-width: 220px !important;
  height: auto !important;
}

[data-story-block] > [data-name] {
  padding: 24px;
}

.docs-story {
  color: #eee;
}

================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2025 Brandon McConnell

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
<h5 align="center"><img src="./.github/render-hooks-logo_full.png?raw=true" width="100%" height="auto" alt="Anchors for Tailwind CSS" /></h5>

<div align="center">
<b><i>RenderHooks (<code>render-hooks</code>)</i></b><br>
<small><i>Use hooks inline in React/JSX</i></small>
</div><br>

RenderHooks lets you place hooks right next to the markup that needs them—no wrapper components, no breaking the [Rules of Hooks](https://react.dev/reference/rules/rules-of-hooks), and zero boilerplate, even when you supply your own custom hooks.

---

- [📖 How it works](#-how-it-works)
- [✨ Features](#-features)
- [🚀 Install](#-install)
- [⚡ Quick start](#-quick-start)
- [🧩 API](#-api)
- [📚 Examples by hook](#-examples-by-hook)
  - [`useState` (React ≥ 16.8)](#usestatereact--168)
  - [`useReducer` (React ≥ 16.8)](#usereducerreact--168)
  - [`useCallback` (React ≥ 16.8)](#usecallbackreact--168)
  - [`useContext` (React ≥ 16.8)](#usecontextreact--168)
  - [`useMemo` (React ≥ 16.8)](#usememoreact--168)
  - [`useEffect` (React ≥ 16.8)](#useeffectreact--168)
  - [`useLayoutEffect` (React ≥ 16.8)](#uselayouteffectreact--168)
  - [`useImperativeHandle` (React ≥ 16.8)](#useimperativehandlereact--168)
  - [`useRef` (React ≥ 16.8)](#userefreact--168)
  - [`useInsertionEffect` (React ≥ 18)](#useinsertioneffectreact--18)
  - [`useId` (React ≥ 18)](#useidreact--18)
  - [`useSyncExternalStore` (React ≥ 18)](#usesyncexternalstorereact--18)
  - [`useDeferredValue` (React ≥ 18)](#usedeferredvaluereact--18)
  - [`useTransition` (React ≥ 18)](#usetransitionreact--18)
  - [`useActionState` (React ≥ 19, experimental in 18)](#useactionstatereact--19-experimental-in-18)
  - [`useFormStatus` (React-DOM ≥ 19)](#useformstatusreact-dom--19)
  - [`use` (awaitable hook, React ≥ 19)](#useawaitable-hook-react--19)
- [🛠 Custom hooks](#-custom-hooks)
- [🧱 Nesting hooks](#-nesting-hooks)
- [🤝 Collaboration](#-collaboration)
  - [How to contribute](#how-to-contribute)

---

## 📖 How it works

1. At runtime RenderHooks scans the installed `react` and `react-dom`
   modules and wraps every export whose name starts with **`use`**.
2. A TypeScript mapped type reproduces *exactly* the same keys from the typings,
   so autocompletion never lies.
3. The callback you give to `<RenderHooks>` (commonly aliased, e.g. `<$>`) is executed during that same render
   pass, keeping the Rules of Hooks intact.
4. Custom hooks are merged in once—stable reference, fully typed.

---

## ✨ Features

| ✔︎ | Description |
|----|-------------|
| **One element** | `<$>` merges every `use*` hook exposed by the consumer's version of **`react` + `react-dom`** into a single helpers object. |
| **Version-adaptive** | Only the hooks that exist in *your* React build appear. Upgrade React → new hooks show up automatically. |
| **Custom-hook friendly** | Pass an object of your own hooks once—full IntelliSense inside the render callback. |
| **100 % type-safe** | No `any`, no `unknown`. Generic signatures flow through the helpers object. |
| **Tiny runtime** | Just an object merge—`<$>` renders nothing to the DOM. |

---

## 🚀 Install

```bash
npm install render-hooks             # or yarn / pnpm / bun
```

RenderHooks lists **`react`** and **`react-dom`** as peer dependencies, so it
always tracks *your* versions.

---

## ⚡ Quick start

```tsx
import $ from 'render-hooks';

export function Counter() {
  return (
    <$>
      {({ useState }) => {
        const [n, set] = useState(0);
        return <button onClick={() => set(n + 1)}>Clicked {n}</button>;
      }}
    </$>
  );
}
```

The hook runs during the same render, so the Rules of Hooks are upheld.

---

## 🧩 API

| Prop        | Type                                               | Description |
|-------------|----------------------------------------------------|-------------|
| `hooks`     | `Record<string, (...args: never[]) => unknown>`    | (optional) custom hooks to expose. |
| `children`  | `(helpers) ⇒ ReactNode`                            | Render callback receiving **all** built-in hooks available in your React version **plus** the custom hooks you supplied. |

---

## 📚 Examples by hook

Below is a **minimal, practical snippet for every built-in hook**.  
Each header lists the **minimum React (or React-DOM) version** required—if your
project uses an older version, that hook simply won't appear in the helpers
object.

> All snippets assume  
> `import $ from 'render-hooks';`

---

### `useState` (React ≥ 16.8)

```tsx
export function UseStateExample() {
  return (
    <$>
      {({ useState }) => {
        const [value, set] = useState('');
        return <input value={value} onChange={(e) => set(e.target.value)} />;
      }}
    </$>
  );
}
```

---

### `useReducer` (React ≥ 16.8)

```tsx
export function UseReducerExample() {
  return (
    <$>
      {({ useReducer }) => {
        const [count, dispatch] = useReducer(
          (s: number, a: 'inc' | 'dec') => (a === 'inc' ? s + 1 : s - 1),
          0,
        );
        return (
          <>
            <button onClick={() => dispatch('dec')}>-</button>
            <span>{count}</span>
            <button onClick={() => dispatch('inc')}>+</button>
          </>
        );
      }}
    </$>
  );
}
```

---

### `useCallback` (React ≥ 16.8)

```tsx
export function UseCallbackExample() {
  return (
    <$>
      {({ useState, useCallback }) => {
        const [txt, setTxt] = useState('');
        const onChange = useCallback(
          (e: React.ChangeEvent<HTMLInputElement>) => setTxt(e.target.value),
          [],
        );
        return <input value={txt} onChange={onChange} />;
      }}
    </$>
  );
}
```

---

### `useContext` (React ≥ 16.8)

```tsx
const ThemeCtx = React.createContext<'light' | 'dark'>('light');

export function UseContextExample() {
  return (
    <ThemeCtx.Provider value="dark">
      <$>
        {({ useContext }) => <p>Theme: {useContext(ThemeCtx)}</p>}
      </$>
    </ThemeCtx.Provider>
  );
}
```

---

### `useMemo` (React ≥ 16.8)

```tsx
export function UseMemoExample() {
  return (
    <$>
      {({ useState, useMemo }) => {
        const [n, setN] = useState(25);
        const fib = useMemo(() => {
          const f = (x: number): number =>
            x <= 1 ? x : f(x - 1) + f(x - 2);
          return f(n);
        }, [n]);
        return (
          <>
            <input
              type="number"
              value={n}
              onChange={(e) => setN(+e.target.value)}
            />
            <p>Fib({n}) = {fib}</p>
          </>
        );
      }}
    </$>
  );
}
```

---

### `useEffect` (React ≥ 16.8)

```tsx
export function UseEffectExample() {
  return (
    <$>
      {({ useState, useEffect }) => {
        const [time, setTime] = useState('');
        useEffect(() => {
          const id = setInterval(
            () => setTime(new Date().toLocaleTimeString()),
            1000,
          );
          return () => clearInterval(id);
        }, []);
        return <p>{time}</p>;
      }}
    </$>
  );
}
```

---

### `useLayoutEffect` (React ≥ 16.8)

```tsx
export function UseLayoutEffectExample() {
  return (
    <$>
      {({ useRef, useLayoutEffect }) => {
        const box = useRef<HTMLDivElement>(null);
        useLayoutEffect(() => {
          box.current!.style.background = '#ffd54f';
        }, []);
        return <div ref={box}> highlighted after layout </div>;
      }}
    </$>
  );
}
```

---

### `useImperativeHandle` (React ≥ 16.8)

```tsx
const Fancy = React.forwardRef<HTMLInputElement>((_, ref) => (
  <$>
    {({ useRef, useImperativeHandle }) => {
      const local = useRef<HTMLInputElement>(null);
      useImperativeHandle(ref, () => ({ focus: () => local.current?.focus() }));
      return <input ref={local} placeholder="Fancy input" />;
    }}
  </$>
));

export function UseImperativeHandleExample() {
  const ref = React.useRef<{ focus: () => void }>(null);
  return (
    <>
      <Fancy ref={ref} />
      <button onClick={() => ref.current?.focus()}>Focus</button>
    </>
  );
}
```

---

### `useRef` (React ≥ 16.8)

```tsx
export function UseRefExample() {
  return (
    <$>
      {({ useRef }) => {
        const input = useRef<HTMLInputElement>(null);
        return (
          <>
            <button onClick={() => input.current?.focus()}>focus</button>
            <input ref={input} />
          </>
        );
      }}
    </$>
  );
}
```

---

### `useInsertionEffect` (React ≥ 18)

```tsx
export function UseInsertionEffectExample() {
  return (
    <$>
      {({ useInsertionEffect }) => {
        useInsertionEffect(() => {
          const style = document.createElement('style');
          style.textContent = `.flash{animation:flash 1s steps(2) infinite;}
            @keyframes flash{to{opacity:.2}}`;
          document.head.append(style);
          return () => style.remove();
        }, []);
        return <p className="flash">flashing text</p>;
      }}
    </$>
  );
}
```

---

### `useId` (React ≥ 18)

```tsx
export function UseIdExample() {
  return (
    <$>
      {({ useId, useState }) => {
        const id = useId();
        const [v, set] = useState('');
        return (
          <>
            <label htmlFor={id}>Name</label>
            <input id={id} value={v} onChange={(e) => set(e.target.value)} />
          </>
        );
      }}
    </$>
  );
}
```

---

### `useSyncExternalStore` (React ≥ 18)

```tsx
export function UseSyncExternalStoreExample() {
  return (
    <$>
      {({ useSyncExternalStore }) => {
        const width = useSyncExternalStore(
          (cb) => {
            window.addEventListener('resize', cb);
            return () => window.removeEventListener('resize', cb);
          },
          () => window.innerWidth,
        );
        return <p>width: {width}px</p>;
      }}
    </$>
  );
}
```

---

### `useDeferredValue` (React ≥ 18)

```tsx
export function UseDeferredValueExample() {
  return (
    <$>
      {({ useState, useDeferredValue }) => {
        const [text, setText] = useState('');
        const deferred = useDeferredValue(text);
        return (
          <>
            <input value={text} onChange={(e) => setText(e.target.value)} />
            <p>deferred: {deferred}</p>
          </>
        );
      }}
    </$>
  );
}
```

---

### `useTransition` (React ≥ 18)

```tsx
export function UseTransitionExample() {
  return (
    <$>
      {({ useState, useTransition }) => {
        const [list, setList] = useState<string[]>([]);
        const [pending, start] = useTransition();
        const filter = (e: React.ChangeEvent<HTMLInputElement>) => {
          const q = e.target.value;
          start(() =>
            setList(
              Array.from({ length: 5_000 }, (_, i) => `Item ${i}`).filter((x) =>
                x.includes(q),
              ),
            ),
          );
        };
        return (
          <>
            <input onChange={filter} placeholder="filter 5 k items" />
            {pending && <p>updating…</p>}
            <p>{list.length} items</p>
          </>
        );
      }}
    </$>
  );
}
```

---

### `useActionState` (React ≥ 19, experimental in 18)

```tsx
export function UseActionStateExample() {
  return (
    <$>
      {({ useActionState }) => {
        const [msg, submit, pending] = useActionState(
          async (_prev: string, data: FormData) => {
            await new Promise((r) => setTimeout(r, 400));
            return data.get('text') as string;
          },
          '',
        );
        return (
          <form action={submit}>
            <input name="text" placeholder="Say hi" />
            <button disabled={pending}>Send</button>
            {msg && <p>You said: {msg}</p>}
          </form>
        );
      }}
    </$>
  );
}
```

---

### `useFormStatus` (React-DOM ≥ 19)

```tsx
export function UseFormStatusExample() {
  return (
    <$>
      {({ useState, useFormStatus }) => {
        const [done, setDone] = useState(false);
        const { pending } = useFormStatus();

        const action = async () => {
          await new Promise((r) => setTimeout(r, 400));
          setDone(true);
        };

        return (
          <form action={action}>
            <button>{pending ? 'Saving…' : 'Save'}</button>
            {done && <p>saved!</p>}
          </form>
        );
      }}
    </$>
  );
}
```

---

### `use` (awaitable hook, React ≥ 19)

```tsx
function fetchQuote() {
  return new Promise<string>((r) =>
    setTimeout(() => r('"Ship early, ship often."'), 800),
  );
}

export function UseAwaitExample() {
  return (
    <$>
      {({ use }) => <blockquote>{use(fetchQuote())}</blockquote>}
    </$>
  );
}
```

---

## 🛠 Custom hooks

Inject any custom hooks once via the `hooks` prop:

```tsx
import $ from 'render-hooks';
import { useToggle, useDebounce } from './myHooks';

export function Example() {
  return (
    <$ hooks={{ useToggle, useDebounce }}>
      {({ useToggle, useDebounce }) => {
        const [open, toggle] = useToggle(false);
        const dOpen = useDebounce(open, 250);
        return (
          <>
            <button onClick={toggle}>toggle</button>
            <p>debounced: {dOpen.toString()}</p>
          </>
        );
      }}
    </$>
  );
}
```

---

## 🧱 Nesting hooks

You can nest `RenderHooks` (`$`) as deeply as you need. Each instance provides its own fresh set of hooks, scoped to its render callback. This is particularly useful for managing item-specific state within loops, where you'd otherwise need to create separate components.

Here's an example where RenderHooks is used to manage state for both levels of a nested list directly within the `.map()` callbacks, and a child can affect a parent RenderHook's state:

```tsx
import React from 'react'; // Needed for useState, useTransition in this example
import $ from 'render-hooks';

type Category = {
  id: number;
  name: string;
  posts: { id: number; title: string }[];
};

const data: Category[] = [
  {
    id: 1,
    name: 'Tech',
    posts: [{ id: 11, title: 'Next-gen CSS' }],
  },
  {
    id: 2,
    name: 'Life',
    posts: [
      { id: 21, title: 'Minimalism' },
      { id: 22, title: 'Travel hacks' },
    ],
  },
];

export function NestedExample() {
  return (
    <ul>
      {data.map((cat) => (
        /* ───── 1️⃣  Outer RenderHooks for each category row ───── */
        <$ key={cat.id}>
          {({ useState, useTransition }) => {
            const [expanded, setExpanded] = useState(false);
            const [likes, setLikes] = useState(0); 
            const [isPending, startTransition] = useTransition();

            return (
              <li>
                <button onClick={() => setExpanded(!expanded)}>
                  {expanded ? '▾' : '▸'} {cat.name} {likes === 0 ? '🖤' : '❤️'.repeat(likes)} ({likes} like{likes === 1 ? '' : 's'})
                  {isPending && ' (updating...)'}
                </button>

                {expanded && (
                  <ul>
                    {cat.posts.map((post) => (
                      /* ───── 2️⃣  Inner RenderHooks per post row ───── */
                      <$ key={post.id}>
                        {({ useState: useItemState }) => {
                          const [liked, setItemLiked] = useItemState(false);

                          const toggleLike = () => {
                            setItemLiked((prev) => {
                              // 🔄 Update outer «likes» using startTransition from the parent RenderHooks
                              const next = !prev;
                              startTransition(() => {
                                setLikes((c) => c + (next ? 1 : -1));
                              });
                              return next;
                            });
                          };

                          return (
                            <li>
                              {post.title}{' '}
                              <button onClick={toggleLike}>
                                {liked ? '❤️ Liked' : '🖤 Like'}
                              </button>
                            </li>
                          );
                        }}
                      </$>
                    ))}
                  </ul>
                )}
              </li>
            );
          }}
        </$>
      ))}
    </ul>
  );
}
```

In this example:
- The main `NestedExample` component does not use RenderHooks directly.
- The **first `.map()`** iterates through `data`. Inside this map, `<$>` is used to give each `category` its own states: `expanded` and `likes`. It also gets `useTransition` to acquire `startTransition`.
- The **second, inner `.map()`** iterates through `cat.posts`. Inside *this* map, another, nested `<$>` is used to give each `post` its own independent `liked` state.
- Crucially, when a post's `toggleLike` function is called, it updates its local `liked` state and then calls `startTransition` (obtained from the parent category's RenderHooks scope) to wrap the update to the parent's `likes` state. 

This demonstrates not only nesting for independent state but also how functions and transition control from a parent RenderHooks instance can be utilized by children that also use RenderHooks, facilitating robust cross-scope communication.

> [!IMPORTANT]
> **Note on `startTransition`**: Using `startTransition` here is important. When an interaction (like clicking "Like") in a nested `RenderHooks` instance needs to update state managed by a parent `RenderHooks` instance, React might issue a warning about "updating one component while rendering another" if the update is synchronous. Wrapping the parent's state update in `startTransition` signals to React that this update can be deferred, preventing the warning and ensuring smoother UI updates. This is a general React pattern applicable when updates across component boundaries (or deeply nested state updates) might occur.

---

## 🤝 Collaboration

RenderHooks is a community-driven project. Every idea, issue, and pull request helps it grow and improve.

Whether you're fixing a typo or implementing a brand-new feature, **you're warmly welcome here!** ✨

### How to contribute

1. ⭐️ **Star the repo** – it helps others discover the project and shows your support.
2. 🐛 **Report bugs / request features** – open an issue and describe the problem or idea. Reproduction steps or code snippets are golden.
3. 📚 **Improve the docs** – spot a typo, unclear wording, or missing example? Submit a quick PR.
4. 👩‍💻 **Send code changes** – bug fixes, performance tweaks, new examples, or custom hooks… big or small, they're all appreciated. If you're unsure, open a draft PR and we'll figure it out together.
5. 💬 **Join the conversation** – comment on issues & PRs, share how you're using RenderHooks, or ask questions. First-time contributors are encouraged to jump in!
6. 📣 **Share it** - if you love it, please share it! I want to grow this tool into something that makes all of our day-to-day lives a bit easier, so no gate-keeping. 

If this would be your **first open-source contribution**, don't hesitate to ask for guidance—I'll happily walk you through the process.

Thank you for making RenderHooks better for everyone! 🙏


================================================
FILE: package.json
================================================
{
  "name": "render-hooks",
  "version": "0.2.0",
  "description": "Inline render-block-stable React hooks",
  "main": "dist/index.js",
  "module": "dist/index.mjs",
  "types": "dist/index.d.ts",
  "exports": {
    ".": {
      "import": "./dist/index.mjs",
      "require": "./dist/index.js",
      "default": "./dist/index.mjs"
    }
  },
  "scripts": {
    "test": "vitest run",
    "test:watch": "vitest",
    "build": "tsdown src/index.tsx --out-dir dist --dts --format cjs,esm",
    "dev": "tsdown src/index.tsx --out-dir dist --dts --watch --format cjs,esm",
    "prepublishOnly": "npm run test && npm run build",
    "storybook": "storybook dev -p 6006",
    "build-storybook": "storybook build",
    "chromatic": "npx chromatic --project-token=$CHROMATIC_PROJECT_TOKEN"
  },
  "files": [
    "dist",
    "README.md",
    "LICENSE"
  ],
  "publishConfig": {
    "access": "public"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/brandonmcconnell/render-hooks.git"
  },
  "keywords": [
    "react",
    "render",
    "inline",
    "hooks"
  ],
  "author": "Brandon McConnell",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/brandonmcconnell/render-hooks/issues"
  },
  "homepage": "https://github.com/brandonmcconnell/render-hooks#readme",
  "peerDependencies": {
    "react": ">=16.8.0",
    "react-dom": ">=16.8.0"
  },
  "devDependencies": {
    "@chromatic-com/storybook": "^3.2.6",
    "@codesandbox/storybook-addon": "^0.2.2",
    "@storybook/addon-essentials": "^8.6.13",
    "@storybook/addon-onboarding": "^8.6.13",
    "@storybook/blocks": "^8.6.13",
    "@storybook/manager-api": "^8.6.13",
    "@storybook/react": "^8.6.13",
    "@storybook/react-vite": "^8.6.13",
    "@storybook/test": "^8.6.13",
    "@storybook/theming": "^8.6.13",
    "@testing-library/jest-dom": "^6.4.8",
    "@testing-library/react": "^16.0.0",
    "@types/node": "^22.15.18",
    "@types/react": "^19.1.4",
    "@types/react-dom": "^19.1.5",
    "@typescript-eslint/eslint-plugin": "^8.32.1",
    "@typescript-eslint/parser": "^8.32.1",
    "@vitejs/plugin-react": "^4.3.1",
    "eslint": "^9.26.0",
    "eslint-config-prettier": "^10.1.5",
    "eslint-plugin-prettier": "^5.4.0",
    "eslint-plugin-storybook": "^0.12.0",
    "happy-dom": "^14.12.3",
    "jsdom": "^26.1.0",
    "npm-run-all": "^4.1.5",
    "patch-package": "^8.0.0",
    "react": "^19.1.0",
    "react-dom": "^19.1.0",
    "storybook": "^8.6.13",
    "tsdown": "^0.11.9",
    "typescript": "^5.8.3",
    "vitest": "^2.0.4"
  },
  "eslintConfig": {
    "extends": [
      "plugin:storybook/recommended"
    ]
  }
}


================================================
FILE: src/index.test.tsx
================================================
/** @vitest-environment jsdom */
import React from 'react';
import { render, screen, fireEvent, waitFor, act as rtlAct, within } from '@testing-library/react';
import '@testing-library/jest-dom';
import $ from './index'; // Assuming RenderHooks is the default export from src/index.tsx
import { useFormStatus as reactDom_useFormStatus } from 'react-dom'; // Import for useFormStatus test
import { vi, describe, it, expect } from 'vitest';

describe('RenderHooks Component', () => {
  describe('Built-in Hooks Examples', () => {
    it('useState example works', () => {
      const UseStateExample = () => (
        <$>
          {({ useState }) => {
            const [value, set] = useState('');
            return <input aria-label="value-input" value={value} onChange={(e) => set(e.target.value)} />;
          }}
        </$>
      );
      render(<UseStateExample />);
      const inputElement = screen.getByLabelText('value-input') as HTMLInputElement;
      expect(inputElement.value).toBe('');
      fireEvent.change(inputElement, { target: { value: 'test' } });
      expect(inputElement.value).toBe('test');
    });

    it('useReducer example works', () => {
      const UseReducerExample = () => (
        <$>
          {({ useReducer }) => {
            const [count, dispatch] = useReducer(
              (s: number, a: 'inc' | 'dec') => (a === 'inc' ? s + 1 : s - 1),
              0,
            );
            return (
              <>
                <button onClick={() => dispatch('dec')}>-</button>
                <span data-testid="count-span">{count}</span>
                <button onClick={() => dispatch('inc')}>+</button>
              </>
            );
          }}
        </$>
      );
      render(<UseReducerExample />);
      const countSpan = screen.getByTestId('count-span');
      const incButton = screen.getByText('+');
      const decButton = screen.getByText('-');

      expect(countSpan).toHaveTextContent('0');
      fireEvent.click(incButton);
      expect(countSpan).toHaveTextContent('1');
      fireEvent.click(decButton);
      fireEvent.click(decButton);
      expect(countSpan).toHaveTextContent('-1');
    });

    it('useCallback example works', () => {
      const mockFn = vi.fn();
      const UseCallbackExample = () => (
        <$>
          {({ useState, useCallback }) => {
            const [txt, setTxt] = useState('');
            const onChange = useCallback(
              (e: React.ChangeEvent<HTMLInputElement>) => {
                setTxt(e.target.value);
                mockFn(e.target.value);
              },
              [],
            );
            return <input aria-label="callback-input" value={txt} onChange={onChange} />;
          }}
        </$>
      );
      render(<UseCallbackExample />);
      const inputElement = screen.getByLabelText('callback-input');
      fireEvent.change(inputElement, { target: { value: 'callback test' } });
      expect(mockFn).toHaveBeenCalledWith('callback test');
    });

    it('useContext example works', () => {
      const ThemeCtx = React.createContext<'light' | 'dark'>('light');
      const UseContextExample = () => (
        <ThemeCtx.Provider value="dark">
          <$>
            {({ useContext }) => <p>Theme: {useContext(ThemeCtx)}</p>}
          </$>
        </ThemeCtx.Provider>
      );
      render(<UseContextExample />);
      expect(screen.getByText('Theme: dark')).toBeInTheDocument();
    });

    it('useMemo example works', () => {
      const UseMemoExample = () => (
        <$>
          {({ useState, useMemo }) => {
            const [n, setN] = useState(5);
            const fib = useMemo(() => {
              const f = (x: number): number => (x <= 1 ? x : f(x - 1) + f(x - 2));
              return f(n);
            }, [n]);
            return (
              <>
                <input
                  type="number"
                  aria-label="memo-input"
                  value={n}
                  onChange={(e) => setN(+e.target.value)}
                />
                <p>Fib({n}) = {fib}</p>
              </>
            );
          }}
        </$>
      );
      render(<UseMemoExample />);
      expect(screen.getByText('Fib(5) = 5')).toBeInTheDocument();
      const inputElement = screen.getByLabelText('memo-input');
      fireEvent.change(inputElement, { target: { value: '6' } });
      expect(screen.getByText('Fib(6) = 8')).toBeInTheDocument();
    });

    it('useEffect example works', async () => {
      vi.useFakeTimers();
      const UseEffectExample = () => (
        <$>
          {({ useState, useEffect }) => {
            const [time, setTime] = useState('');
            useEffect(() => {
              const id = setInterval(
                () => {
                  const newTime = new Date().toLocaleTimeString();
                  setTime(newTime);
                },
                1000,
              );
              return () => clearInterval(id);
            }, []);
            return <p>{time || 'loading...'}</p>;
          }}
        </$>
      );
      render(<UseEffectExample />);
      expect(screen.getByText('loading...')).toBeInTheDocument();
      rtlAct(() => {
        vi.advanceTimersByTime(1000);
      });
      vi.advanceTimersByTime(0); // Ensure setInterval callback and React re-render are processed

      // Synchronous check for "loading..." to be gone
      expect(screen.queryByText('loading...')).not.toBeInTheDocument();

      // Second synchronous check for the time text
      vi.advanceTimersByTime(0); // Ensure microtasks from first assertion are flushed
      expect(screen.getByText(/:/)).toBeInTheDocument();

      vi.useRealTimers();
    }, 5000); // <-- Test-specific timeout of 5000ms

    it('useLayoutEffect example works', () => {
      const UseLayoutEffectExample = () => (
        <$>
          {({ useRef, useLayoutEffect }) => {
            const box = useRef<HTMLDivElement>(null);
            useLayoutEffect(() => {
              if (box.current) {
                box.current.style.background = 'rgb(255, 213, 79)'; // #ffd54f
              }
            }, []);
            return <div ref={box} data-testid="layout-box"> highlighted after layout </div>;
          }}
        </$>
      );
      render(<UseLayoutEffectExample />);
      const boxElement = screen.getByTestId('layout-box');
      expect(boxElement).toHaveStyle('background: rgb(255, 213, 79)');
    });

    it('useImperativeHandle example works', () => {
      const Fancy = React.forwardRef<{ focus: () => void }>((_, ref) => (
        <$>
          {({ useRef, useImperativeHandle }) => {
            const local = useRef<HTMLInputElement>(null);
            useImperativeHandle(ref, () => ({
              focus: () => local.current?.focus(),
            }));
            return <input ref={local} placeholder="Fancy input" defaultValue="fancy value" />;
          }}
        </$>
      ));
      Fancy.displayName = 'Fancy';

      const UseImperativeHandleExample = () => {
        const ref = React.useRef<{ focus: () => void }>(null);
        return (
          <>
            <Fancy ref={ref} />
            <button onClick={() => ref.current?.focus()}>Focus Fancy</button>
          </>
        );
      };
      render(<UseImperativeHandleExample />);
      const button = screen.getByText('Focus Fancy');
      const inputInsideFancy = screen.getByPlaceholderText('Fancy input') as HTMLInputElement;
      expect(inputInsideFancy).not.toHaveFocus();
      fireEvent.click(button);
      expect(inputInsideFancy).toHaveFocus();
    });

    it('useRef example works', () => {
      const UseRefExample = () => (
        <$>
          {({ useRef }) => {
            const input = useRef<HTMLInputElement>(null);
            return (
              <>
                <button onClick={() => input.current?.focus()}>focus</button>
                <input ref={input} aria-label="ref-input" />
              </>
            );
          }}
        </$>
      );
      render(<UseRefExample />);
      const inputElement = screen.getByLabelText('ref-input');
      const button = screen.getByText('focus');
      expect(inputElement).not.toHaveFocus();
      fireEvent.click(button);
      expect(inputElement).toHaveFocus();
    });

    it('useInsertionEffect example works', () => {
      const UseInsertionEffectExample = () => (
        <$>
          {({ useInsertionEffect }) => {
            useInsertionEffect(() => {
              const style = document.createElement('style');
              style.id = 'flash-style';
              style.textContent = '.flash{animation:flash 1s steps(2) infinite;}\n@keyframes flash{to{opacity:.2}}';
              document.head.append(style);
              return () => {
                document.getElementById('flash-style')?.remove();
              };
            }, []);
            return <p className="flash" data-testid="flash-text">flashing text</p>;
          }}
        </$>
      );
      const { unmount } = render(<UseInsertionEffectExample />);
      const styleElement = document.getElementById('flash-style');
      expect(styleElement).toBeInTheDocument();
      expect(styleElement?.textContent).toContain('.flash{animation:flash 1s steps(2) infinite;}');
      expect(screen.getByTestId('flash-text')).toHaveClass('flash');
      unmount();
      expect(document.getElementById('flash-style')).not.toBeInTheDocument();
    });

    it('useId example works', () => {
      const UseIdExample = () => (
        <$>
          {({ useId, useState }) => {
            const id = useId();
            const [v, set] = useState('');
            return (
              <>
                <label htmlFor={id}>Name</label>
                <input id={id} value={v} onChange={(e) => set(e.target.value)} />
              </>
            );
          }}
        </$>
      );
      render(<UseIdExample />);
      const label = screen.getByText('Name');
      const input = screen.getByLabelText('Name');
      expect(input.id).toBe(label.getAttribute('for'));
      expect(input.id).toMatch(/^(:r\d+:|«r\d+?»)$/);
    });

    it('useSyncExternalStore example works', () => {
      const listeners: Array<() => void> = [];
      const subscribe = (cb: () => void) => {
        window.addEventListener('resize', cb);
        listeners.push(cb);
        return () => {
          window.removeEventListener('resize', cb);
          const index = listeners.indexOf(cb);
          if (index > -1) listeners.splice(index, 1);
        };
      };
      const getSnapshot = () => window.innerWidth;

      const originalInnerWidth = window.innerWidth;

      const UseSyncExternalStoreExample = () => (
        <$>
          {({ useSyncExternalStore }) => {
            const width = useSyncExternalStore(
              subscribe,
              getSnapshot,
            );
            return <p>width: {width}px</p>;
          }}
        </$>
      );

      render(<UseSyncExternalStoreExample />);
      expect(screen.getByText(`width: ${originalInnerWidth}px`)).toBeInTheDocument();

      rtlAct(() => {
        (window as any).innerWidth = 1024;
        window.dispatchEvent(new Event('resize'));
      });
      expect(screen.getByText('width: 1024px')).toBeInTheDocument();

      rtlAct(() => {
        (window as any).innerWidth = originalInnerWidth;
         window.dispatchEvent(new Event('resize'));
      });
       expect(screen.getByText(`width: ${originalInnerWidth}px`)).toBeInTheDocument();
    });

    it('useDeferredValue example works', async () => {
      vi.useFakeTimers();
      const UseDeferredValueExample = () => (
        <$>
          {({ useState, useDeferredValue }) => {
            const [text, setText] = useState('');
            const deferred = useDeferredValue(text);
            return (
              <>
                <input aria-label="deferred-input" value={text} onChange={(e) => setText(e.target.value)} />
                <p data-testid="deferred-output">deferred: {deferred}</p>
              </>
            );
          }}
        </$>
      );
      render(<UseDeferredValueExample />);
      const input = screen.getByLabelText('deferred-input');
      const output = screen.getByTestId('deferred-output');

      expect(output).toHaveTextContent('deferred:');

      rtlAct(() => {
        fireEvent.change(input, { target: { value: 'hello' } });
      });

      // Allow deferred value to update
      rtlAct(() => {
        vi.runAllTimers();
      });
      vi.advanceTimersByTime(0); // Flush tasks

      expect(output).toHaveTextContent('deferred: hello');
      vi.useRealTimers();
    }, 5000); // Test-specific timeout

    it('useTransition example works', async () => {
      vi.useFakeTimers();
      const UseTransitionExample = () => (
        <$>
          {({ useState, useTransition }) => {
            const [list, setList] = useState<string[]>([]);
            const [pending, start] = useTransition();
            const filter = (e: React.ChangeEvent<HTMLInputElement>) => {
              const q = e.target.value;
              start(() => {
                // Simulate async work for the transition with a timer
                setTimeout(() => {
                  const fullList = Array.from({ length: 10 }, (_, i) => `Item ${i}`);
                  setList(fullList.filter((x) => x.includes(q)));
                }, 10); 
              });
            };
            return (
              <>
                <input onChange={filter} placeholder="filter items" aria-label="transition-input"/>
                {/* We will not assert the pending state directly due to timing complexities with fake timers */}
                {/* {pending && <p>updating…</p>} */}
                <p data-testid="transition-list-length">{list.length} items</p>
                <ul>{list.map(item => <li key={item}>{item}</li>)}</ul>
              </>
            );
          }}
        </$>
      );
      render(<UseTransitionExample />);
      const input = screen.getByLabelText('transition-input');
      const listLength = screen.getByTestId('transition-list-length');

      expect(listLength).toHaveTextContent('0 items');

      rtlAct(() => {
        fireEvent.change(input, { target: { value: 'Item 1' } });
      });

      // Removed: Check for pending state, as it's too transient with fake timers here.

      // Complete the transition work by running all timers
      rtlAct(() => {
        vi.runAllTimers(); // This executes the setTimeout(..., 10) inside startTransition
      });
      
      // Flush any pending macrotasks from React updates
      vi.advanceTimersByTime(0);

      // Assertions for the final state
      expect(screen.queryByText('updating…')).not.toBeInTheDocument(); // Should be gone now
      expect(listLength).toHaveTextContent('1 items');
      expect(screen.getByText('Item 1')).toBeInTheDocument();
      
      vi.useRealTimers();
    }, 10000); 

    it('useActionState example works', async () => {
      vi.useFakeTimers();

      const mockAction = vi.fn(async (_prev: string | null, data: FormData) => {
        await new Promise((r) => setTimeout(r, 50));
        return `Said: ${data.get('text') as string}`;
      });

      const UseActionStateExample = () => (
        <$>
          {({ useActionState }) => {
            const [msg, submit, pending] = useActionState<string | null, FormData>(mockAction, null);
            return (
              <form 
                action={submit as unknown as ((payload: FormData) => void) | string}
              >
                <input name="text" placeholder="Say hi" aria-label="action-state-input" />
                <button type="submit" disabled={pending}>Send</button>
                {pending && <p>Submitting...</p>}
                {msg && <p data-testid="action-state-msg">{msg}</p>}
              </form>
            );
          }}
        </$>
      );

      render(<UseActionStateExample />);
      const input = screen.getByLabelText('action-state-input');
      const button = screen.getByText('Send');

      rtlAct(() => {
        fireEvent.change(input, { target: { value: 'Hello Action' } });
        fireEvent.click(button);
      });

      // Assert pending state immediately after action is dispatched
      expect(screen.getByText('Submitting...')).toBeInTheDocument();
      expect(button).toBeDisabled();

      // Wait for the action to complete and UI to update
      // This act block covers the resolution of timers and the promise from mockAction,
      // and the subsequent state updates in useActionState.
      await rtlAct(async () => {
        vi.runAllTimers(); // Resolve the setTimeout within mockAction
        await Promise.resolve(); // Ensure promise from mockAction (if any) resolves and is processed
      });

      // Assert final state: React should have updated after the act block
      expect(screen.queryByText('Submitting...')).not.toBeInTheDocument();

      // Final check for mock calls after waitFor confirms UI is stable
      expect(mockAction).toHaveBeenCalledTimes(1);
      vi.useRealTimers();
    }, 5000);

    it('useFormStatus example works (within a form component)', async () => {
      vi.useFakeTimers(); // Good practice, though mockFormAction is manually resolved
      let formActionResolver: () => void = () => {}; // Initialize to satisfy TS
      const mockFormAction = vi.fn(async () => {
        await new Promise<void>(resolve => { formActionResolver = resolve; });
      });

      const SubmitButton = () => {
        const { pending } = reactDom_useFormStatus();
        return <button type="submit" disabled={pending}>{pending ? 'Saving…' : 'Save'}</button>;
      };

      const StatusDisplay = () => {
         const { pending } = reactDom_useFormStatus();
         return pending ? <p>Form is pending...</p> : <p>Form is idle.</p>;
      };

      const UseFormStatusExampleForm = () => {
         const [done, setDone] = React.useState(false);
         const formActionAttr = async (payload: FormData) => {
           await mockFormAction();
           setDone(true);
         };
        return (
           <$>
            {() => (
              <form action={formActionAttr as unknown as ((payload: FormData) => void) | string}>
                <StatusDisplay/>
                <SubmitButton />
                {done && <p data-testid="form-done-msg">saved!</p>}
              </form>
            )}
          </$>
        );
      };

      render(<UseFormStatusExampleForm />);
      const saveButton = screen.getByText('Save');
      expect(screen.getByText('Form is idle.')).toBeInTheDocument();

      rtlAct(() => {
        fireEvent.click(saveButton);
      });

      expect(mockFormAction).toHaveBeenCalledTimes(1);
      // Assert pending state for SubmitButton and StatusDisplay
      expect(screen.getByText('Saving…')).toBeInTheDocument();
      expect(screen.getByText('Form is pending...')).toBeInTheDocument();
      expect(saveButton).toBeDisabled();

      // Resolve the form action and wait for state updates
      await rtlAct(async () => {
        formActionResolver(); // Resolve the mockFormAction's promise
        await Promise.resolve(); // Ensure promise propagation and related state updates are processed
      });
      vi.advanceTimersByTime(0); // Flush any final React updates if necessary

      // Assert final state
      expect(screen.getByText('Save')).toBeInTheDocument();
      expect(screen.getByText('Form is idle.')).toBeInTheDocument();
      expect(screen.getByTestId('form-done-msg')).toHaveTextContent('saved!');
      expect(saveButton).not.toBeDisabled();
      vi.useRealTimers();
    }, 5000);

    it("'use' (awaitable hook) example works", async () => {
      vi.useFakeTimers();
      const fetchQuote = () => new Promise<string>((r) => setTimeout(() => r('"Ship early, ship often."' ), 50));
      const fetchQuotePromise = fetchQuote(); // Get the promise instance beforehand

      // Ensure the promise is resolved before rendering the component that uses it.
      // Use act to be safe, though direct await might also work if no React updates are expected here.
      await rtlAct(async () => {
        vi.runAllTimers();       // Resolve the setTimeout in fetchQuote
        await fetchQuotePromise; // Explicitly wait for the promise to settle
      });

      const UseAwaitExample = () => (
        <React.Suspense fallback={<p>Loading quote...</p>}>
          <$>
            {({ use }) => {
                const quote = use(fetchQuotePromise);
                return <blockquote>{quote}</blockquote>;
            }}
          </$>
        </React.Suspense>
      );

      // Wrap render in act to handle the synchronous resolution of the pre-resolved promise by the 'use' hook
      await rtlAct(async () => {
        render(<UseAwaitExample />);
        // Since the promise is pre-resolved, React should synchronously render the result.
        // We might need a microtask tick for React to fully process if there are internal updates.
        await Promise.resolve(); 
      });
      
      // With a pre-resolved promise and render in act, the content should be immediately available
      expect(screen.getByText('"Ship early, ship often."' )).toBeInTheDocument();
      // And the fallback should not have been rendered (or be gone)
      expect(screen.queryByText('Loading quote...')).not.toBeInTheDocument();

      vi.useRealTimers();
    }, 5000);
  });

  describe('Custom Hooks Example', () => {
    it('custom hooks can be provided and used', async () => {
      vi.useFakeTimers(); // Added for debounce
      const useToggle = (initialValue = false): [boolean, () => void] => {
        const [value, setValue] = React.useState(initialValue);
        const toggle = React.useCallback(() => setValue((v) => !v), []);
        return [value, toggle];
      };
      const useDebounce = <T,>(value: T, delay: number): T => {
        const [debouncedValue, setDebouncedValue] = React.useState<T>(value);
        React.useEffect(() => {
          const handler = setTimeout(() => setDebouncedValue(value), delay);
          return () => clearTimeout(handler);
        }, [value, delay]);
        return debouncedValue;
      };

      const customHooks = { useToggle, useDebounce };

      const CustomHooksExample = () => (
        <$ hooks={customHooks}>
          {({ useToggle, useDebounce }) => {
            const [open, toggle] = useToggle(false);
            const dOpen = useDebounce(open, 50);
            return (
              <>
                <button onClick={toggle}>toggle</button>
                <p data-testid="custom-open-state">open: {open.toString()}</p>
                <p data-testid="custom-debounced-state">debounced: {dOpen.toString()}</p>
              </>
            );
          }}
        </$>
      );
      render(<CustomHooksExample />);
      const toggleButton = screen.getByText('toggle');
      const openState = screen.getByTestId('custom-open-state');
      const debouncedState = screen.getByTestId('custom-debounced-state');

      expect(openState).toHaveTextContent('open: false');
      expect(debouncedState).toHaveTextContent('debounced: false');

      fireEvent.click(toggleButton);
      expect(openState).toHaveTextContent('open: true');
      expect(debouncedState).toHaveTextContent('debounced: false');

      // Wait for debounce to complete
      rtlAct(() => {
        vi.runAllTimers(); // Process setTimeout in useDebounce
      });
      vi.advanceTimersByTime(0); // Ensure React re-renders and microtasks are flushed

      // Now assert the debounced state
      expect(debouncedState).toHaveTextContent('debounced: true');
      vi.useRealTimers(); // Added for debounce
    }, 5000); // Added test-specific timeout
  });
}); 

// --- New Test Suite for NestedImpactfulExample ---

type Category = {
  id: number;
  name: string;
  posts: { id: number; title: string }[];
};

const data: Category[] = [
  {
    id: 1,
    name: 'Tech',
    posts: [{ id: 11, title: 'Next-gen CSS' }],
  },
  {
    id: 2,
    name: 'Life',
    posts: [
      { id: 21, title: 'Minimalism' },
      { id: 22, title: 'Travel hacks' },
    ],
  },
];

// This is the component from README.md
const NestedImpactfulExample = () => {
  return (
    <ul>
      {data.map((cat) => (
        /* ───── 1️⃣  Outer RenderHooks for each category row ───── */
        <$ key={cat.id}>
          {({ useState, useTransition }) => {
            const [expanded, setExpanded] = useState(false);
            const [likes, setLikes] = useState(0); 
            const [isPending, startTransition] = useTransition();

            return (
              <li>
                <button onClick={() => setExpanded(!expanded)}>
                  {expanded ? '▾' : '▸'} {cat.name} ({likes} like{likes === 1 ? '' : 's'})
                  {isPending && ' (updating...)'}
                </button>

                {expanded && (
                  <ul>
                    {cat.posts.map((post) => (
                      /* ───── 2️⃣  Inner RenderHooks per post row ───── */
                      <$ key={post.id}>
                        {({ useState: useItemState }) => {
                          const [liked, setItemLiked] = useItemState(false);

                          const toggleLike = () => {
                            setItemLiked((prev) => {
                              const next = !prev;
                              // 🔄 update outer «likes» when this post toggles, wrapped in outer transition
                              startTransition(() => {
                                setLikes((c) => c + (next ? 1 : -1));
                              });
                              return next;
                            });
                          };

                          return (
                            <li>
                              {post.title}{' '}
                              <button onClick={toggleLike}>
                                {liked ? '♥︎ Liked' : '♡ Like'}
                              </button>
                            </li>
                          );
                        }}
                      </$>
                    ))}
                  </ul>
                )}
              </li>
            );
          }}
        </$>
      ))}
    </ul>
  );
};

describe('NestedImpactfulExample from README', () => {
  it('should render initial state correctly', () => {
    render(<NestedImpactfulExample />);
    expect(screen.getByText(/▸ Tech \(0 likes\)/)).toBeInTheDocument();
    expect(screen.getByText(/▸ Life \(0 likes\)/)).toBeInTheDocument();
    expect(screen.queryByText('Next-gen CSS')).not.toBeInTheDocument();
    expect(screen.queryByText('Minimalism')).not.toBeInTheDocument();
  });

  it('should expand and collapse categories', () => {
    render(<NestedImpactfulExample />);
    const techCategoryButton = screen.getByText(/▸ Tech \(0 likes\)/);
    fireEvent.click(techCategoryButton);
    expect(screen.getByText(/▾ Tech \(0 likes\)/)).toBeInTheDocument();
    expect(screen.getByText('Next-gen CSS')).toBeInTheDocument();
    fireEvent.click(techCategoryButton);
    expect(screen.getByText(/▸ Tech \(0 likes\)/)).toBeInTheDocument();
    expect(screen.queryByText('Next-gen CSS')).not.toBeInTheDocument();
  });

  it('should allow liking/unliking posts and update category likes', () => {
    render(<NestedImpactfulExample />);
    const techCategoryButton = screen.getByText(/▸ Tech \(0 likes\)/);
    fireEvent.click(techCategoryButton); // Expand Tech category

    const techPostLikeButton = screen.getByRole('button', { name: '♡ Like' });
    expect(techPostLikeButton).toBeInTheDocument();

    // Like the post
    rtlAct(() => {
      fireEvent.click(techPostLikeButton);
    });
    expect(screen.getByText(/Tech \(1 like\)/)).toBeInTheDocument();
    expect(screen.getByRole('button', { name: '♥︎ Liked' })).toBeInTheDocument();

    // Unlike the post
    rtlAct(() => {
      fireEvent.click(techPostLikeButton); // techPostLikeButton reference should still be valid as text content changed but not the element itself
    });
    expect(screen.getByText(/Tech \(0 likes\)/)).toBeInTheDocument();
    expect(screen.getByRole('button', { name: '♡ Like' })).toBeInTheDocument();
  });

  it('should handle multiple posts and maintain independent likes within a category', async () => {
    render(<NestedImpactfulExample />);
    const lifeCategoryButton = screen.getByText(/▸ Life \(0 likes\)/);
    fireEvent.click(lifeCategoryButton); // Expand Life category

    const minimalismListItem = screen.getByText('Minimalism').closest('li')!;
    const travelHacksListItem = screen.getByText('Travel hacks').closest('li')!;

    const minimalismLikeButton = within(minimalismListItem).getByRole('button', { name: /Like|Liked/i });
    const travelHacksLikeButton = within(travelHacksListItem).getByRole('button', { name: /Like|Liked/i });
    
    // Like Minimalism
    await rtlAct(async () => {
      fireEvent.click(minimalismLikeButton);
    });
    expect(screen.getByText(/Life \(1 like\)/)).toBeInTheDocument();
    expect(minimalismLikeButton).toHaveTextContent('♥︎ Liked');
    expect(travelHacksLikeButton).toHaveTextContent('♡ Like');

    // Like Travel hacks
    await rtlAct(async () => {
      fireEvent.click(travelHacksLikeButton);
    });
    expect(screen.getByText(/Life \(2 likes\)/)).toBeInTheDocument();
    expect(minimalismLikeButton).toHaveTextContent('♥︎ Liked');
    expect(travelHacksLikeButton).toHaveTextContent('♥︎ Liked');

    // Unlike Minimalism
    await rtlAct(async () => {
      fireEvent.click(minimalismLikeButton);
    });
    expect(screen.getByText(/Life \(1 like\)/)).toBeInTheDocument();
    expect(minimalismLikeButton).toHaveTextContent('♡ Like');
    expect(travelHacksLikeButton).toHaveTextContent('♥︎ Liked');
  });

  it('should maintain independent state between categories', async () => {
    render(<NestedImpactfulExample />);
    const techCategoryButton = screen.getByText(/▸ Tech \(0 likes\)/);
    const lifeCategoryButton = screen.getByText(/▸ Life \(0 likes\)/);

    // Expand Tech and like its post
    fireEvent.click(techCategoryButton);
    const techPostListItem = screen.getByText('Next-gen CSS').closest('li')!;
    const techPostLikeButton = within(techPostListItem).getByRole('button', { name: /Like|Liked/i });
    await rtlAct(async () => {
      fireEvent.click(techPostLikeButton);
    });
    expect(screen.getByText(/Tech \(1 like\)/)).toBeInTheDocument();
    expect(screen.getByText(/Life \(0 likes\)/)).toBeInTheDocument(); // Life category unchanged

    // Expand Life and like one of its posts
    fireEvent.click(lifeCategoryButton);
    const minimalismListItemForLife = screen.getByText('Minimalism').closest('li')!;
    const minimalismLikeButtonInLife = within(minimalismListItemForLife).getByRole('button', { name: /Like|Liked/i });
    await rtlAct(async () => {
      fireEvent.click(minimalismLikeButtonInLife);
    });
    expect(screen.getByText(/Tech \(1 like\)/)).toBeInTheDocument(); // Tech category unchanged
    expect(screen.getByText(/Life \(1 like\)/)).toBeInTheDocument();
    
    // Check that Life category expansion did not affect Tech posts visibility
    expect(screen.getByText('Next-gen CSS')).toBeInTheDocument(); // Tech post still visible
  });
}); 

================================================
FILE: src/index.tsx
================================================
import React from 'react';
import ReactDOM from 'react-dom';

/* ----------------------------------------------------------- *
 * 1 ▸ helper types                                             *
 * ----------------------------------------------------------- */

// local utility for "is a function"
type Fn = (...args: any[]) => any;

/** Map an object T ➜ only its `use*` keys that are functions. */
type ExtractHooks<T> = {
  [K in keyof T as K extends `use${string}`
    ? T[K] extends Fn
      ? K
      : never
    : never]: T[K] extends Fn ? T[K] : never;
};

/* ----------------------------------------------------------- *
 * 2 ▸ runtime collector that preserves static types            *
 * ----------------------------------------------------------- */
function collectHooks<T>(src: T): ExtractHooks<T> {
  const out = {} as ExtractHooks<T>;

  for (const key in src) {
    if (key.startsWith('use')) {
      const fn = (src as Record<string, unknown>)[key];
      if (typeof fn === 'function') {
        (out as Record<string, Fn>)[key] = fn as Fn;
      }
    }
  }
  return out;
}

/* ----------------------------------------------------------- *
 * 3 ▸ core helpers = hooks found in the *installed* libs       *
 * ----------------------------------------------------------- */
const coreHelpers = {
  ...collectHooks(React),
  ...collectHooks(ReactDOM),
};

type CoreHelpers = typeof coreHelpers;

/* ----------------------------------------------------------- *
 * 4 ▸ default component                                       *
 * ----------------------------------------------------------- */
export default function RenderHooks<
  TValue extends Record<string, Fn> = {},
>(props: {
  hooks?: TValue;
  children: (helpers: CoreHelpers & TValue) => React.ReactNode;
}): React.ReactElement {
  const { hooks, children } = props;
  const helpers = React.useMemo(
    () => ({ ...coreHelpers, ...(hooks ?? {}) }),
    [hooks],
  ) as CoreHelpers & TValue;
  return <>{children(helpers)}</>;
}


================================================
FILE: src/stories/00-QuickStart.stories.tsx
================================================
import type { Meta } from '@storybook/react';
import React from 'react'; // React is implicitly used by JSX and useState
import $ from '../index';

// Copied from README.md Quick Start
export function Counter() {
  return (
    <$>
      {({ useState }) => {
        const [n, set] = useState(0);
        return <button onClick={() => set(n + 1)}>Clicked {n}</button>;
      }}
    </$>
  );
}

const meta: Meta<typeof Counter> = {
  title: 'Examples/Quick Start',
  component: Counter,
  parameters: {
    layout: 'centered',
  },
  tags: ['autodocs'],
};

export default meta;
// type Story = StoryObj<typeof meta>;

// export const Default: Story = {}; 

================================================
FILE: src/stories/01-BuiltInHooks.stories.tsx
================================================
import type { Meta } from '@storybook/react';
import React from 'react'; // For createContext, useRef, etc.
import $ from '../index'; // Adjust path as necessary
import { useFormStatus as reactDom_useFormStatus } from 'react-dom'; // For useFormStatus example

// --- useState --- 
export function Example_useState() {
  return (
    <$>
      {({ useState }) => {
        const [value, set] = useState('');
        return (
          <>
            <input aria-label="useState-input" value={value} onChange={(e) => set(e.target.value)} placeholder="useState: type here..." />
            <p>Value: "{value}"</p>
          </>
        );
      }}
    </$>
  );
}
Example_useState.storyName = 'useState';

// --- useReducer --- 
export function Example_useReducer() {
  return (
    <$>
      {({ useReducer }) => {
        const [count, dispatch] = useReducer(
          (s: number, a: 'inc' | 'dec') => (a === 'inc' ? s + 1 : s - 1),
          0,
        );
        return (
          <div style={{ display: 'flex', alignItems: 'center', gap: '5px' }}>
            <button onClick={() => dispatch('dec')}>-</button>
            <span data-testid="count-span">{count} (useReducer)</span>
            <button onClick={() => dispatch('inc')}>+</button>
          </div>
        );
      }}
    </$>
  );
}
Example_useReducer.storyName = 'useReducer';

// --- useCallback --- 
export function Example_useCallback() {
  return (
    <$>
      {({ useState, useCallback }) => {
        const [txt, setTxt] = useState('');
        // eslint-disable-next-line @typescript-eslint/no-unused-vars
        const handleChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => setTxt(e.target.value), []);
        return (
          <>
            <input aria-label="useCallback-input" value={txt} onChange={handleChange} placeholder="useCallback: type here..." />
            <p>Value: "{txt}"</p>
          </>
        );
      }}
    </$>
  );
}
Example_useCallback.storyName = 'useCallback';

// --- useContext --- 
const ThemeCtx = React.createContext<'light' | 'dark'>('light');
export function Example_useContext() {
  return (
    <ThemeCtx.Provider value="dark">
      <$>
        {({ useContext }) => <p>Theme (useContext): {useContext(ThemeCtx)}</p>}
      </$>
    </ThemeCtx.Provider>
  );
}
Example_useContext.storyName = 'useContext';

// --- useMemo --- 
export function Example_useMemo() {
  return (
    <$>
      {({ useState, useMemo, useRef }) => {
        const [n, setN] = useState(5); // Smaller default for story

        // A ref-based cache that survives across renders and different n values
        const cache = useRef<Map<number, number>>(new Map());

        const fib = useMemo(() => {
          const memoFib = (k: number): number => {
            const m = cache.current;
            if (m.has(k)) return m.get(k)!;
            const val = k <= 1 ? k : memoFib(k - 1) + memoFib(k - 2);
            m.set(k, val);
            return val;
          };
          return memoFib(n);
        }, [n]);
        return (
          <div>
            <input
              type="number"
              aria-label="useMemo-input"
              value={n}
              onChange={(e) => setN(+e.target.value)}
              style={{width: '50px'}}
            />
            <p>Fib({n}) (cached with useMemo+ref) = {fib}</p>
          </div>
        );
      }}
    </$>
  );
}
Example_useMemo.storyName = 'useMemo';

// --- useEffect --- 
export function Example_useEffect() {
  return (
    <$>
      {({ useState, useEffect }) => {
        const [time, setTime] = useState('loading...');
        useEffect(() => {
          const id = setInterval(() => setTime(new Date().toLocaleTimeString()), 1000);
          return () => clearInterval(id);
        }, []);
        return <p>Time (useEffect): {time}</p>;
      }}
    </$>
  );
}
Example_useEffect.storyName = 'useEffect';

// --- useLayoutEffect --- 
export function Example_useLayoutEffect() {
  return (
    <$>
      {({ useRef, useLayoutEffect }) => {
        const box = useRef<HTMLDivElement>(null);
        useLayoutEffect(() => {
          if (box.current) {
            box.current.style.background = '#ffd54f';
            box.current.style.color = '#000';
          }
        }, []);
        return <div ref={box} style={{ padding: '5px', border: '1px solid grey'}}> Div (useLayoutEffect) highlighted after layout </div>;
      }}
    </$>
  );
}
Example_useLayoutEffect.storyName = 'useLayoutEffect';

// --- useImperativeHandle --- 
// (moved Collapsible implementation inside Example_useImperativeHandle below)
export function Example_useImperativeHandle() {
  // Define the imperative handle type and the Collapsible component locally so everything is self-contained.
  type CollapsibleHandle = {
    open: () => void;
    close: () => void;
    toggle: () => void;
  };

  const Collapsible = React.useMemo(() => {
    const C = React.forwardRef<CollapsibleHandle, { title: string; children?: React.ReactNode }>(
      ({ title, children }, ref) => (
        <$>
          {({ useState, useImperativeHandle }) => {
            const [open, setOpen] = useState(false);

            useImperativeHandle(ref, () => ({
              open: () => setOpen(true),
              close: () => setOpen(false),
              toggle: () => setOpen((o) => !o),
            }));

            return (
              <div>
                <button onClick={() => setOpen((o) => !o)}>
                  {open ? 'Hide' : 'Show'} {title}
                </button>
                {open && (
                  <div style={{ border: '1px solid grey', marginTop: '5px', padding: '5px' }}>{children}</div>
                )}
              </div>
            );
          }}
        </$>
      ),
    );
    return C;
  }, []);

  const panelRef = React.useRef<CollapsibleHandle>(null);

  return (
    <div>
      <Collapsible ref={panelRef} title="Details">
        <p>This content can be toggled imperatively using the buttons below or via the panel header.</p>
      </Collapsible>

      <div style={{ marginTop: '10px', display: 'flex', gap: '5px' }}>
        <button onClick={() => panelRef.current?.open()}>Open (imperative)</button>
        <button onClick={() => panelRef.current?.close()}>Close (imperative)</button>
        <button onClick={() => panelRef.current?.toggle()}>Toggle (imperative)</button>
      </div>
    </div>
  );
}
Example_useImperativeHandle.storyName = 'useImperativeHandle';

// --- useRef --- 
export function Example_useRef() {
  return (
    <$>
      {({ useRef }) => {
        const inputEl = useRef<HTMLInputElement>(null);
        return (
          <div>
            <input ref={inputEl} aria-label="useRef-input" placeholder="useRef: input" />
            <button onClick={() => inputEl.current?.focus()}>Focus Input</button>
          </div>
        );
      }}
    </$>
  );
}
Example_useRef.storyName = 'useRef';

// --- useInsertionEffect --- 
export function Example_useInsertionEffect() {
  const [show, setShow] = React.useState(true);
  const id = 'insertion-effect-style';
  return (
    <div>
      <button onClick={() => setShow(s => !s)}>{show ? 'Hide' : 'Show'} (unmounts component)</button>
      {show && <$>
        {({ useInsertionEffect }) => {
          useInsertionEffect(() => {
            const style = document.createElement('style');
            style.id = id;
            style.textContent = `.flash-insertion{animation:flash-insertion 1s steps(2) infinite;} @keyframes flash-insertion{to{opacity:.2}}`;
            document.head.append(style);
            return () => {
              document.getElementById(id)?.remove();
            };
          }, []);
          return <p className="flash-insertion">Flashing text (useInsertionEffect)</p>;
        }}
      </$>}
    </div>
  );
}
Example_useInsertionEffect.storyName = 'useInsertionEffect';

// --- useId --- 
export function Example_useId() {
  return (
    <$>
      {({ useId, useState }) => {
        const id = useId();
        const [val, setVal] = useState('');
        return (
          <div>
            <label htmlFor={id}>Name (useId):</label> (id: <code style={{ background: '#555', color: '#fff' }}>{id}</code>)
            <br />
            <input id={id} value={val} onChange={(e) => setVal(e.target.value)} style={{marginLeft: '5px'}}/>  (id: <code style={{ background: '#ddd' }}>{id}</code>)
          </div>
        );
      }}
    </$>
  );
}
Example_useId.storyName = 'useId';

// --- useSyncExternalStore --- 
export function Example_useSyncExternalStore() {
  return (
    <$>
      {({ useSyncExternalStore }) => {
        const width = useSyncExternalStore(
          (cb) => {
            window.addEventListener('resize', cb);
            return () => window.removeEventListener('resize', cb);
          },
          () => window.innerWidth,
          () => -1 // server snapshot
        );
        return <p>Window width (useSyncExternalStore): {width}px</p>;
      }}
    </$>
  );
}
Example_useSyncExternalStore.storyName = 'useSyncExternalStore';

// --- useDeferredValue --- 
export function Example_useDeferredValue() {
  return (
    <$>
      {({ useState, useDeferredValue }) => {
        const [text, setText] = useState('');
        const deferred = useDeferredValue(text);
        return (
          <div>
            <input aria-label="useDeferredValue-input" value={text} onChange={(e) => setText(e.target.value)} placeholder="useDeferredValue: type..."/>
            <p>Deferred: {deferred}</p>
          </div>
        );
      }}
    </$>
  );
}
Example_useDeferredValue.storyName = 'useDeferredValue';

// --- useTransition --- 
export function Example_useTransition() {
  return (
    <$>
      {({ useState, useTransition, useMemo }) => {
        // Create a fixed list of sample products once (more practical than generic numbers)
        const items = useMemo(
          () => [
            'Alligator',
            'Bear',
            'Cat',
            'Dog',
            'Elephant',
            'Fox',
            'Giraffe',
            'Horse',
            'Iguana',
            'Jaguar',
            'Kangaroo',
            'Lion',
            'Monkey',
            'Newt',
            'Owl',
            'Penguin',
            'Quail',
            'Rabbit',
            'Shark',
            'Tiger',
          ],
          [],
        );
        const [list, setList] = useState<string[]>(items);
        const [pending, start] = useTransition();

        const filter = (e: React.ChangeEvent<HTMLInputElement>) => {
          const q = e.target.value.toLowerCase();
          start(() => setList(items.filter((x) => x.toLowerCase().includes(q))));
        };
        return (
          <div>
            {/* Show the full list so users know what can be searched */}
            <p style={{ maxWidth: '600px' }}>
              All items:&nbsp;
              <span style={{ fontStyle: 'italic' }}>{items.join(', ')}</span>
            </p>
            <input aria-label="useTransition-filter" onChange={filter} placeholder="Filter products..." />
            {pending && <p>Updating...</p>}
            <p>{list.length} item{list.length === 1 ? '' : 's'} found. {list.length === items.length ? '(no items filtered)' : ''}</p>
            {/* Show the filtered items */}
            {list.length > 0 && <p style={{ maxWidth: '600px' }}>{list.join(', ')}</p>}
          </div>
        );
      }}
    </$>
  );
}
Example_useTransition.storyName = 'useTransition';

// --- useActionState --- 
export function Example_useActionState() {
  if (!React.useActionState) {
    return <p>React.useActionState is not available in this version of React.</p>;
  }
  return (
    <$>
      {({ useActionState }) => {
        const [msg, submit, pending] = useActionState(
          async (_prev: string | null, data: FormData) => {
            await new Promise((r) => setTimeout(r, 400));
            return data.get('text') as string;
          },
          null,
        );
        return (
          <form action={submit}>
            <input name="text" placeholder="useActionState: Say hi" />
            <button type="submit" disabled={pending}>Send</button>
            {msg && <p>You said: {msg}</p>}
          </form>
        );
      }}
    </$>
  );
}
Example_useActionState.storyName = 'useActionState';

// --- useFormStatus --- 
// Note: react-dom useFormStatus is used here as react's might not be available/same
const FormStatusButton = () => {
  // useFormStatus must be used within a <form>
  // So, we extract it to a sub-component for RenderHooks to access.
  return (
    <$>
      {({ useFormStatus }) => {
        const { pending } = useFormStatus ? useFormStatus() : { pending: false }; // Check if useFormStatus exists
        return <button type="submit" disabled={pending}>{pending ? 'Saving (useFormStatus)...' : 'Save'}</button>;
      }}
    </$>
  );
};
export function Example_useFormStatus() {
  if (!reactDom_useFormStatus) { // Check if the imported one exists
    return <p>ReactDOM.useFormStatus is not available in this version of React DOM.</p>;
  }
  return (
    <$>
      {({ useState }) => {
        const [done, setDone] = useState(false);
        const action = async () => {
          await new Promise((r) => setTimeout(r, 400));
          setDone(true);
          setTimeout(() => setDone(false), 2000); // Reset for story
        };
        return (
          <form action={action}>
            <FormStatusButton />
            {done && <p>Saved!</p>}
          </form>
        );
      }}
    </$>
  );
}
Example_useFormStatus.storyName = 'useFormStatus';

// --- use (awaitable hook) --- 
export function Example_use() {
  if (!React.use) {
    return <p>React.use is not available in this version of React.</p>;
  }
  // Helper function for the 'use' example
  let quotePromise: Promise<string>;
  const fetchQuote = () => {
    quotePromise = new Promise<string>((resolve) =>
      setTimeout(() => resolve('"Ship early, ship often." (from use hook)'), 800),
    );
    return quotePromise;
  };
  /**
   * To make this storybook-friendly and re-runnable, we reset the promise on each render.
   * In a real app, you might fetch once or based on props.
   */
  fetchQuote(); 

  return (
    <React.Suspense fallback={<p>Loading quote (use hook)...</p>}>
      <$>
        {({ use }) => {
          return <blockquote>{use(quotePromise)}</blockquote>;
        }}
      </$>
    </React.Suspense>
  );
}
Example_use.storyName = 'use';

// --- meta --- 
const meta: Meta = {
  title: 'Examples/Built-in Hooks',
  tags: ['autodocs'],
  parameters: {
    layout: 'top',
  },
  decorators: [
    (Story) => (
      <div style={{ display: 'flex', flexDirection: 'column', gap: '20px' }}>
        <Story />
      </div>
    ),
  ],
};
export default meta;


================================================
FILE: src/stories/02-CustomHooks.stories.tsx
================================================
import type { Meta } from '@storybook/react';
import React from 'react'; // For useState
import $ from '../index';

// Dummy custom hooks (as defined in README, assuming they would be in ./myHooks)
// For Storybook, we'll define them directly in this file.
const useToggle = (initialValue = false): [boolean, () => void] => {
  const [state, setState] = React.useState(initialValue);
  const toggle = React.useCallback(() => setState((s) => !s), []);
  return [state, toggle];
};

const useDebounce = <T,>(value: T, delay: number): T => {
  const [debouncedValue, setDebouncedValue] = React.useState<T>(value);
  React.useEffect(() => {
    const handler = setTimeout(() => {
      setDebouncedValue(value);
    }, delay);
    return () => {
      clearTimeout(handler);
    };
  }, [value, delay]);
  return debouncedValue;
};
// End of dummy custom hooks

// Copied from README.md Custom Hooks section
export function CustomHooksExample() {
  return (
    <$ hooks={{ useToggle, useDebounce }}>
      {/* @ts-ignore */}
      {({ useToggle, useDebounce, useState }) => { // Added useState for completeness
        const [open, toggle] = useToggle(false);
        const dOpen = useDebounce(open, 250);
        const [count, setCount] = useState(0); // Example of using a built-in hook alongside

        return (
          <div>
            <button onClick={toggle}>Toggle Custom Hook</button>
            <p> 'open' (from useToggle): {open.toString()}</p>
            <p>Debounced 'open' (from useDebounce): {dOpen.toString()}</p>
            <hr />
            <button onClick={() => setCount(c => c + 1)}>Increment Built-in Hook ({count})</button>
          </div>
        );
      }}
    </$>
  );
}
CustomHooksExample.storyName = 'Using Custom Hooks';

const meta: Meta<typeof CustomHooksExample> = {
  title: 'Examples/Custom Hooks',
  component: CustomHooksExample,
  parameters: {
    layout: 'centered',
  },
  tags: ['autodocs'],
};

export default meta;

================================================
FILE: src/stories/03-NestingRenderHooks.stories.tsx
================================================
import type { Meta } from '@storybook/react';
import React from 'react'; // Needed for useState, useTransition in this example
import $ from '../index';

// Copied from README.md Nesting RenderHooks section
type Category = {
  id: number;
  name: string;
  posts: { id: number; title: string }[];
};

const data: Category[] = [
  {
    id: 1,
    name: 'Tech',
    posts: [{ id: 11, title: 'Next-gen CSS' }],
  },
  {
    id: 2,
    name: 'Life',
    posts: [
      { id: 21, title: 'Minimalism' },
      { id: 22, title: 'Travel hacks' },
    ],
  },
  {
    id: 3,
    name: 'Food',
    posts: [
      { id: 31, title: 'Quick Dinners' },
      { id: 32, title: 'Baking Bread' },
      { id: 33, title: 'Global Cuisine' },
    ],
  },
];

export function NestedExample() {
  return (
    <ul style={{ listStyle: 'none', paddingLeft: '0'}}>
      {data.map((cat) => (
        /* ───── 1️⃣  Outer RenderHooks for each category row ───── */
        <$ key={cat.id}>
          {({ useState, useTransition }) => {
            const [expanded, setExpanded] = useState(false);
            const [likes, setLikes] = useState(0);
            const [isPending, startTransition] = useTransition();

            return (
              <li style={{ marginBottom: '10px', border: '1px solid #eee', padding: '10px' }}>
                <button onClick={() => setExpanded(!expanded)} style={{ fontWeight: 'bold', marginBottom: '5px'}}>
                  {expanded ? '▾' : '▸'} {cat.name} {likes === 0 ? '🖤' : '❤️'.repeat(likes)} ({likes} like{likes === 1 ? '' : 's'})
                  {isPending && ' (updating...)'}
                </button>

                {expanded && (
                  <ul style={{ listStyle: 'none', paddingLeft: '20px'}}>
                    {cat.posts.map((post) => (
                      /* ───── 2️⃣  Inner RenderHooks per post row ───── */
                      <$ key={post.id}>
                        {({ useState: useItemState }) => {
                          const [liked, setItemLiked] = useItemState(false);

                          const toggleLike = () => {
                            setItemLiked((prev) => {
                              const next = !prev;
                              // 🔄 Update outer «likes» using startTransition from the parent RenderHooks
                              startTransition(() => {
                                setLikes((c) => c + (next ? 1 : -1));
                              });
                              return next;
                            });
                          };

                          return (
                            <li style={{ marginTop: '5px'}}>
                              {post.title}{' '}
                              <button onClick={toggleLike}>
                                {liked ? '❤️ Liked' : '🖤 Like'}
                              </button>
                            </li>
                          );
                        }}
                      </$>
                    ))}
                  </ul>
                )}
              </li>
            );
          }}
        </$>
      ))}
    </ul>
  );
}
NestedExample.storyName = 'Using Nested Hooks';
// End of copied code

const meta: Meta<typeof NestedExample> = {
  title: 'Examples/Nested Hooks',
  component: NestedExample,
  parameters: {
    layout: 'padded',
  },
  tags: ['autodocs'],
};

export default meta;


================================================
FILE: src/stories/Header.tsx
================================================
import React from 'react';

import { Button } from './Button';
import './header.css';

type User = {
  name: string;
};

export interface HeaderProps {
  user?: User;
  onLogin?: () => void;
  onLogout?: () => void;
  onCreateAccount?: () => void;
}

export const Header = ({ user, onLogin, onLogout, onCreateAccount }: HeaderProps) => (
  <header>
    <div className="storybook-header">
      <div>
        <svg width="32" height="32" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
          <g fill="none" fillRule="evenodd">
            <path
              d="M10 0h12a10 10 0 0110 10v12a10 10 0 01-10 10H10A10 10 0 010 22V10A10 10 0 0110 0z"
              fill="#FFF"
            />
            <path
              d="M5.3 10.6l10.4 6v11.1l-10.4-6v-11zm11.4-6.2l9.7 5.5-9.7 5.6V4.4z"
              fill="#555AB9"
            />
            <path
              d="M27.2 10.6v11.2l-10.5 6V16.5l10.5-6zM15.7 4.4v11L6 10l9.7-5.5z"
              fill="#91BAF8"
            />
          </g>
        </svg>
        <h1>Acme</h1>
      </div>
      <div>
        {user ? (
          <>
            <span className="welcome">
              Welcome, <b>{user.name}</b>!
            </span>
            <Button size="small" onClick={onLogout} label="Log out" />
          </>
        ) : (
          <>
            <Button size="small" onClick={onLogin} label="Log in" />
            <Button primary size="small" onClick={onCreateAccount} label="Sign up" />
          </>
        )}
      </div>
    </div>
  </header>
);


================================================
FILE: src/stories/Page.tsx
================================================
import React from 'react';

import { Header } from './Header';
import './page.css';

type User = {
  name: string;
};

export const Page: React.FC = () => {
  const [user, setUser] = React.useState<User>();

  return (
    <article>
      <Header
        user={user}
        onLogin={() => setUser({ name: 'Jane Doe' })}
        onLogout={() => setUser(undefined)}
        onCreateAccount={() => setUser({ name: 'Jane Doe' })}
      />

      <section className="storybook-page">
        <h2>Pages in Storybook</h2>
        <p>
          We recommend building UIs with a{' '}
          <a href="https://componentdriven.org" target="_blank" rel="noopener noreferrer">
            <strong>component-driven</strong>
          </a>{' '}
          process starting with atomic components and ending with pages.
        </p>
        <p>
          Render pages with mock data. This makes it easy to build and review page states without
          needing to navigate to them in your app. Here are some handy patterns for managing page
          data in Storybook:
        </p>
        <ul>
          <li>
            Use a higher-level connected component. Storybook helps you compose such data from the
            "args" of child component stories
          </li>
          <li>
            Assemble data in the page component from your services. You can mock these services out
            using Storybook.
          </li>
        </ul>
        <p>
          Get a guided tutorial on component-driven development at{' '}
          <a href="https://storybook.js.org/tutorials/" target="_blank" rel="noopener noreferrer">
            Storybook tutorials
          </a>
          . Read more in the{' '}
          <a href="https://storybook.js.org/docs" target="_blank" rel="noopener noreferrer">
            docs
          </a>
          .
        </p>
        <div className="tip-wrapper">
          <span className="tip">Tip</span> Adjust the width of the canvas with the{' '}
          <svg width="10" height="10" viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg">
            <g fill="none" fillRule="evenodd">
              <path
                d="M1.5 5.2h4.8c.3 0 .5.2.5.4v5.1c-.1.2-.3.3-.4.3H1.4a.5.5 0 01-.5-.4V5.7c0-.3.2-.5.5-.5zm0-2.1h6.9c.3 0 .5.2.5.4v7a.5.5 0 01-1 0V4H1.5a.5.5 0 010-1zm0-2.1h9c.3 0 .5.2.5.4v9.1a.5.5 0 01-1 0V2H1.5a.5.5 0 010-1zm4.3 5.2H2V10h3.8V6.2z"
                id="a"
                fill="#999"
              />
            </g>
          </svg>
          Viewports addon in the toolbar
        </div>
      </section>
    </article>
  );
};


================================================
FILE: src/stories/button.css
================================================
.storybook-button {
  display: inline-block;
  cursor: pointer;
  border: 0;
  border-radius: 3em;
  font-weight: 700;
  line-height: 1;
  font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;
}
.storybook-button--primary {
  background-color: #555ab9;
  color: white;
}
.storybook-button--secondary {
  box-shadow: rgba(0, 0, 0, 0.15) 0px 0px 0px 1px inset;
  background-color: transparent;
  color: #333;
}
.storybook-button--small {
  padding: 10px 16px;
  font-size: 12px;
}
.storybook-button--medium {
  padding: 11px 20px;
  font-size: 14px;
}
.storybook-button--large {
  padding: 12px 24px;
  font-size: 16px;
}


================================================
FILE: src/stories/header.css
================================================
.storybook-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  border-bottom: 1px solid rgba(0, 0, 0, 0.1);
  padding: 15px 20px;
  font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;
}

.storybook-header svg {
  display: inline-block;
  vertical-align: top;
}

.storybook-header h1 {
  display: inline-block;
  vertical-align: top;
  margin: 6px 0 6px 10px;
  font-weight: 700;
  font-size: 20px;
  line-height: 1;
}

.storybook-header button + button {
  margin-left: 10px;
}

.storybook-header .welcome {
  margin-right: 10px;
  color: #333;
  font-size: 14px;
}


================================================
FILE: src/stories/page.css
================================================
.storybook-page {
  margin: 0 auto;
  padding: 48px 20px;
  max-width: 600px;
  color: #333;
  font-size: 14px;
  line-height: 24px;
  font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;
}

.storybook-page h2 {
  display: inline-block;
  vertical-align: top;
  margin: 0 0 4px;
  font-weight: 700;
  font-size: 32px;
  line-height: 1;
}

.storybook-page p {
  margin: 1em 0;
}

.storybook-page a {
  color: inherit;
}

.storybook-page ul {
  margin: 1em 0;
  padding-left: 30px;
}

.storybook-page li {
  margin-bottom: 8px;
}

.storybook-page .tip {
  display: inline-block;
  vertical-align: top;
  margin-right: 10px;
  border-radius: 1em;
  background: #e7fdd8;
  padding: 4px 12px;
  color: #357a14;
  font-weight: 700;
  font-size: 11px;
  line-height: 12px;
}

.storybook-page .tip-wrapper {
  margin-top: 40px;
  margin-bottom: 40px;
  font-size: 13px;
  line-height: 20px;
}

.storybook-page .tip-wrapper svg {
  display: inline-block;
  vertical-align: top;
  margin-top: 3px;
  margin-right: 4px;
  width: 12px;
  height: 12px;
}

.storybook-page .tip-wrapper svg path {
  fill: #1ea7fd;
}


================================================
FILE: tsconfig.json
================================================
{
  "compilerOptions": {
    /* Visit https://aka.ms/tsconfig to read more about this file */

    /* Projects */
    // "incremental": true,                              /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
    // "composite": true,                                /* Enable constraints that allow a TypeScript project to be used with project references. */
    // "tsBuildInfoFile": "./.tsbuildinfo",              /* Specify the path to .tsbuildinfo incremental compilation file. */
    // "disableSourceOfProjectReferenceRedirect": true,  /* Disable preferring source files instead of declaration files when referencing composite projects. */
    // "disableSolutionSearching": true,                 /* Opt a project out of multi-project reference checking when editing. */
    // "disableReferencedProjectLoad": true,             /* Reduce the number of projects loaded automatically by TypeScript. */

    /* Language and Environment */
    "target": "es2022",                                  /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
    "lib": ["es2022", "dom"],
    "jsx": "react",                                      /* Specify what JSX code is generated. */
    // "libReplacement": true,                           /* Enable lib replacement. */
    // "experimentalDecorators": true,                   /* Enable experimental support for legacy experimental decorators. */
    // "emitDecoratorMetadata": true,                    /* Emit design-type metadata for decorated declarations in source files. */
    // "jsxFactory": "",                                 /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
    // "jsxFragmentFactory": "",                         /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
    // "jsxImportSource": "",                            /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
    // "reactNamespace": "",                             /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
    // "noLib": true,                                    /* Disable including any library files, including the default lib.d.ts. */
    // "useDefineForClassFields": true,                  /* Emit ECMAScript-standard-compliant class fields. */
    "moduleDetection": "force",

    /* Modules */
    "module": "NodeNext",                                /* Specify what module code is generated. */
    // "rootDir": "./",                                  /* Specify the root folder within your source files. */
    "moduleResolution": "nodenext",                        /* Specify how TypeScript looks up a file from a given module specifier. */
    // "baseUrl": "./",                                  /* Specify the base directory to resolve non-relative module names. */
    // "paths": {},                                      /* Specify a set of entries that re-map imports to additional lookup locations. */
    // "rootDirs": [],                                   /* Allow multiple folders to be treated as one when resolving modules. */
    // "typeRoots": [],                                  /* Specify multiple folders that act like './node_modules/@types'. */
    // "types": [],                                      /* Specify type package names to be included without being referenced in a source file. */
    // "allowUmdGlobalAccess": true,                     /* Allow accessing UMD globals from modules. */
    // "moduleSuffixes": [],                             /* List of file name suffixes to search when resolving a module. */
    // "allowImportingTsExtensions": true,               /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */
    // "rewriteRelativeImportExtensions": true,          /* Rewrite '.ts', '.tsx', '.mts', and '.cts' file extensions in relative import paths to their JavaScript equivalent in output files. */
    // "resolvePackageJsonExports": true,                /* Use the package.json 'exports' field when resolving package imports. */
    // "resolvePackageJsonImports": true,                /* Use the package.json 'imports' field when resolving imports. */
    // "customConditions": [],                           /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */
    // "noUncheckedSideEffectImports": true,             /* Check side effect imports. */
    "resolveJsonModule": true,
    // "allowArbitraryExtensions": true,                 /* Enable importing files with any extension, provided a declaration file is present. */
    // "noResolve": true,                                /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */

    /* JavaScript Support */
    "allowJs": true,                                  /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
    // "checkJs": true,                                  /* Enable error reporting in type-checked JavaScript files. */
    // "maxNodeModuleJsDepth": 1,                        /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */

    /* Emit */
    "declaration": true,                                 /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
    // "declarationMap": true,                           /* Create sourcemaps for d.ts files. */
    // "emitDeclarationOnly": true,                      /* Only output d.ts files and not JavaScript files. */
    "sourceMap": true,                                /* Create source map files for emitted JavaScript files. */
    // "inlineSourceMap": true,                          /* Include sourcemap files inside the emitted JavaScript. */
    // "noEmit": true,                                   /* Disable emitting files from a compilation. */
    // "outFile": "./",                                  /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
    "outDir": "dist",                                    /* Specify an output folder for all emitted files. */
    // "removeComments": true,                           /* Disable emitting comments. */
    // "importHelpers": true,                            /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
    // "downlevelIteration": true,                       /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
    // "sourceRoot": "",                                 /* Specify the root path for debuggers to find the reference source code. */
    // "mapRoot": "",                                    /* Specify the location where debugger should locate map files instead of generated locations. */
    // "inlineSources": true,                            /* Include source code in the sourcemaps inside the emitted JavaScript. */
    // "emitBOM": true,                                  /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
    // "newLine": "crlf",                                /* Set the newline character for emitting files. */
    // "stripInternal": true,                            /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
    // "noEmitHelpers": true,                            /* Disable generating custom helper functions like '__extends' in compiled output. */
    // "noEmitOnError": true,                            /* Disable emitting files if any type checking errors are reported. */
    // "preserveConstEnums": true,                       /* Disable erasing 'const enum' declarations in generated code. */
    // "declarationDir": "./",                           /* Specify the output directory for generated declaration files. */

    /* Interop Constraints */
    "isolatedModules": true,                          /* Ensure that each file can be safely transpiled without relying on other imports. */
    // "verbatimModuleSyntax": true,                     /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */
    // "isolatedDeclarations": true,                     /* Require sufficient annotation on exports so other tools can trivially generate declaration files. */
    // "erasableSyntaxOnly": true,                       /* Do not allow runtime constructs that are not part of ECMAScript. */
    // "allowSyntheticDefaultImports": true,             /* Allow 'import x from y' when a module doesn't have a default export. */
    "esModuleInterop": true,                             /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
    // "preserveSymlinks": true,                         /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
    "forceConsistentCasingInFileNames": true,            /* Ensure that casing is correct in imports. */

    /* Type Checking */
    "strict": true,                                      /* Enable all strict type-checking options. */
    // "noImplicitAny": true,                            /* Enable error reporting for expressions and declarations with an implied 'any' type. */
    // "strictNullChecks": true,                         /* When type checking, take into account 'null' and 'undefined'. */
    // "strictFunctionTypes": true,                      /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
    // "strictBindCallApply": true,                      /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
    // "strictPropertyInitialization": true,             /* Check for class properties that are declared but not set in the constructor. */
    // "strictBuiltinIteratorReturn": true,              /* Built-in iterators are instantiated with a 'TReturn' type of 'undefined' instead of 'any'. */
    // "noImplicitThis": true,                           /* Enable error reporting when 'this' is given the type 'any'. */
    // "useUnknownInCatchVariables": true,               /* Default catch clause variables as 'unknown' instead of 'any'. */
    // "alwaysStrict": true,                             /* Ensure 'use strict' is always emitted. */
    // "noUnusedLocals": true,                           /* Enable error reporting when local variables aren't read. */
    // "noUnusedParameters": true,                       /* Raise an error when a function parameter isn't read. */
    // "exactOptionalPropertyTypes": true,               /* Interpret optional property types as written, rather than adding 'undefined'. */
    // "noImplicitReturns": true,                        /* Enable error reporting for codepaths that do not explicitly return in a function. */
    // "noFallthroughCasesInSwitch": true,               /* Enable error reporting for fallthrough cases in switch statements. */
    "noUncheckedIndexedAccess": true,                 /* Add 'undefined' to a type when accessed using an index. */
    // "noImplicitOverride": true,                       /* Ensure overriding members in derived classes are marked with an override modifier. */
    // "noPropertyAccessFromIndexSignature": true,       /* Enforces using indexed accessors for keys declared using an indexed type. */
    // "allowUnusedLabels": true,                        /* Disable error reporting for unused labels. */
    // "allowUnreachableCode": true,                     /* Disable error reporting for unreachable code. */

    /* Completeness */
    // "skipDefaultLibCheck": true,                      /* Skip type checking .d.ts files that are included with TypeScript. */
    "skipLibCheck": true                                 /* Skip type checking all .d.ts files. */
  },
  "include": ["src", "vite-env.d.ts", ".storybook"],
  "exclude": ["node_modules", "dist"]
}


================================================
FILE: vite-env.d.ts
================================================
/// <reference types="vite/client" />
/// <reference types="vite/types/importMeta.d.ts" />

// Extend the existing ImportMetaEnv interface with your custom variables
interface ImportMetaEnv {
  readonly STORYBOOK_CODESANDBOX_TOKEN: string;
}

interface ImportMeta {
  readonly env: ImportMetaEnv;
}


================================================
FILE: vitest.config.ts
================================================
import { defineConfig } from 'vitest/config';
import react from '@vitejs/plugin-react'; // Required for Vitest to process React components

export default defineConfig({
  plugins: [react()],
  test: {
    globals: true,
    environment: 'happy-dom', // or 'jsdom'
    include: ['src/**/*.test.{ts,tsx}'],
    testTimeout: 30000, // Increased timeout to 30 seconds
    coverage: {
      provider: 'v8', // or 'istanbul'
      reporter: ['text', 'json', 'html'],
      reportsDirectory: './coverage',
      include: ['src/**/*.{ts,tsx}'],
      exclude: [
        'src/**/*.test.{ts,tsx}',
        'src/**/index.{ts,tsx}', // Usually, the main export file doesn't need direct coverage if its parts are tested
        'vitest.config.ts',
        'vitest.setup.ts',
      ],
    },
  },
}); 
Download .txt
gitextract_ii3d9lst/

├── .github/
│   └── workflows/
│       └── chromatic.yml
├── .gitignore
├── .npmignore
├── .storybook/
│   ├── Theme.js
│   ├── main.ts
│   ├── manager.ts
│   ├── preview.ts
│   └── storybook.css
├── LICENSE
├── README.md
├── package.json
├── src/
│   ├── index.test.tsx
│   ├── index.tsx
│   └── stories/
│       ├── 00-QuickStart.stories.tsx
│       ├── 01-BuiltInHooks.stories.tsx
│       ├── 02-CustomHooks.stories.tsx
│       ├── 03-NestingRenderHooks.stories.tsx
│       ├── Header.tsx
│       ├── Page.tsx
│       ├── assets/
│       │   └── avif-test-image.avif
│       ├── button.css
│       ├── header.css
│       └── page.css
├── tsconfig.json
├── vite-env.d.ts
└── vitest.config.ts
Download .txt
SYMBOL INDEX (32 symbols across 9 files)

FILE: src/index.test.tsx
  type Category (line 638) | type Category = {

FILE: src/index.tsx
  type Fn (line 9) | type Fn = (...args: any[]) => any;
  type ExtractHooks (line 12) | type ExtractHooks<T> = {
  function collectHooks (line 23) | function collectHooks<T>(src: T): ExtractHooks<T> {
  type CoreHelpers (line 45) | type CoreHelpers = typeof coreHelpers;
  function RenderHooks (line 50) | function RenderHooks<

FILE: src/stories/00-QuickStart.stories.tsx
  function Counter (line 6) | function Counter() {

FILE: src/stories/01-BuiltInHooks.stories.tsx
  function Example_useState (line 7) | function Example_useState() {
  function Example_useReducer (line 25) | function Example_useReducer() {
  function Example_useCallback (line 47) | function Example_useCallback() {
  function Example_useContext (line 68) | function Example_useContext() {
  function Example_useMemo (line 80) | function Example_useMemo() {
  function Example_useEffect (line 118) | function Example_useEffect() {
  function Example_useLayoutEffect (line 135) | function Example_useLayoutEffect() {
  function Example_useImperativeHandle (line 155) | function Example_useImperativeHandle() {
  function Example_useRef (line 212) | function Example_useRef() {
  function Example_useInsertionEffect (line 230) | function Example_useInsertionEffect() {
  function Example_useId (line 256) | function Example_useId() {
  function Example_useSyncExternalStore (line 276) | function Example_useSyncExternalStore() {
  function Example_useDeferredValue (line 296) | function Example_useDeferredValue() {
  function Example_useTransition (line 315) | function Example_useTransition() {
  function Example_useActionState (line 373) | function Example_useActionState() {
  function Example_useFormStatus (line 414) | function Example_useFormStatus() {
  function Example_use (line 440) | function Example_use() {

FILE: src/stories/02-CustomHooks.stories.tsx
  function CustomHooksExample (line 28) | function CustomHooksExample() {

FILE: src/stories/03-NestingRenderHooks.stories.tsx
  type Category (line 6) | type Category = {
  function NestedExample (line 37) | function NestedExample() {

FILE: src/stories/Header.tsx
  type User (line 6) | type User = {
  type HeaderProps (line 10) | interface HeaderProps {

FILE: src/stories/Page.tsx
  type User (line 6) | type User = {

FILE: vite-env.d.ts
  type ImportMetaEnv (line 5) | interface ImportMetaEnv {
  type ImportMeta (line 9) | interface ImportMeta {
Condensed preview — 26 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (110K chars).
[
  {
    "path": ".github/workflows/chromatic.yml",
    "chars": 940,
    "preview": "name: \"Chromatic\"\n\non: push\n\njobs:\n  chromatic:\n    name: Run Chromatic\n    runs-on: ubuntu-latest\n    steps:\n      - na"
  },
  {
    "path": ".gitignore",
    "chars": 1979,
    "preview": "# Dependencies\nnode_modules/\n\n# Build output\ndist/\n\n# Logs\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\nlerna-debug.lo"
  },
  {
    "path": ".npmignore",
    "chars": 307,
    "preview": "# Source files\nsrc/\n\n# Config files\n.eslintignore\n.eslintrc.js\n.prettierrc.js\n.prettierignore\n\n# Tests\n__tests__/\nsrc/**"
  },
  {
    "path": ".storybook/Theme.js",
    "chars": 330,
    "preview": "import { create } from '@storybook/theming';\n \nexport default create({\n  base: 'dark',\n  brandTitle: 'RenderHooks',\n  br"
  },
  {
    "path": ".storybook/main.ts",
    "chars": 1105,
    "preview": "import type { StorybookConfig } from '@storybook/react-vite';\n\nconst config: StorybookConfig = {\n  \"stories\": [\n    \"../"
  },
  {
    "path": ".storybook/manager.ts",
    "chars": 137,
    "preview": "import { addons } from '@storybook/manager-api';\nimport theme from './Theme';\nimport './storybook.css';\n \naddons.setConf"
  },
  {
    "path": ".storybook/preview.ts",
    "chars": 1554,
    "preview": "import type { Preview } from '@storybook/react'\nimport theme from './Theme'\nimport './storybook.css';\n\nconst preview: Pr"
  },
  {
    "path": ".storybook/storybook.css",
    "chars": 456,
    "preview": "@import url('https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=s"
  },
  {
    "path": "LICENSE",
    "chars": 1074,
    "preview": "MIT License\n\nCopyright (c) 2025 Brandon McConnell\n\nPermission is hereby granted, free of charge, to any person obtaining"
  },
  {
    "path": "README.md",
    "chars": 19210,
    "preview": "<h5 align=\"center\"><img src=\"./.github/render-hooks-logo_full.png?raw=true\" width=\"100%\" height=\"auto\" alt=\"Anchors for "
  },
  {
    "path": "package.json",
    "chars": 2623,
    "preview": "{\n  \"name\": \"render-hooks\",\n  \"version\": \"0.2.0\",\n  \"description\": \"Inline render-block-stable React hooks\",\n  \"main\": \""
  },
  {
    "path": "src/index.test.tsx",
    "chars": 31400,
    "preview": "/** @vitest-environment jsdom */\nimport React from 'react';\nimport { render, screen, fireEvent, waitFor, act as rtlAct, "
  },
  {
    "path": "src/index.tsx",
    "chars": 2003,
    "preview": "import React from 'react';\nimport ReactDOM from 'react-dom';\n\n/* -------------------------------------------------------"
  },
  {
    "path": "src/stories/00-QuickStart.stories.tsx",
    "chars": 656,
    "preview": "import type { Meta } from '@storybook/react';\nimport React from 'react'; // React is implicitly used by JSX and useState"
  },
  {
    "path": "src/stories/01-BuiltInHooks.stories.tsx",
    "chars": 14832,
    "preview": "import type { Meta } from '@storybook/react';\nimport React from 'react'; // For createContext, useRef, etc.\nimport $ fro"
  },
  {
    "path": "src/stories/02-CustomHooks.stories.tsx",
    "chars": 1965,
    "preview": "import type { Meta } from '@storybook/react';\nimport React from 'react'; // For useState\nimport $ from '../index';\n\n// D"
  },
  {
    "path": "src/stories/03-NestingRenderHooks.stories.tsx",
    "chars": 3404,
    "preview": "import type { Meta } from '@storybook/react';\nimport React from 'react'; // Needed for useState, useTransition in this e"
  },
  {
    "path": "src/stories/Header.tsx",
    "chars": 1525,
    "preview": "import React from 'react';\n\nimport { Button } from './Button';\nimport './header.css';\n\ntype User = {\n  name: string;\n};\n"
  },
  {
    "path": "src/stories/Page.tsx",
    "chars": 2580,
    "preview": "import React from 'react';\n\nimport { Header } from './Header';\nimport './page.css';\n\ntype User = {\n  name: string;\n};\n\ne"
  },
  {
    "path": "src/stories/button.css",
    "chars": 642,
    "preview": ".storybook-button {\n  display: inline-block;\n  cursor: pointer;\n  border: 0;\n  border-radius: 3em;\n  font-weight: 700;\n "
  },
  {
    "path": "src/stories/header.css",
    "chars": 623,
    "preview": ".storybook-header {\n  display: flex;\n  justify-content: space-between;\n  align-items: center;\n  border-bottom: 1px solid"
  },
  {
    "path": "src/stories/page.css",
    "chars": 1125,
    "preview": ".storybook-page {\n  margin: 0 auto;\n  padding: 48px 20px;\n  max-width: 600px;\n  color: #333;\n  font-size: 14px;\n  line-h"
  },
  {
    "path": "tsconfig.json",
    "chars": 12610,
    "preview": "{\n  \"compilerOptions\": {\n    /* Visit https://aka.ms/tsconfig to read more about this file */\n\n    /* Projects */\n    //"
  },
  {
    "path": "vite-env.d.ts",
    "chars": 299,
    "preview": "/// <reference types=\"vite/client\" />\n/// <reference types=\"vite/types/importMeta.d.ts\" />\n\n// Extend the existing Impor"
  },
  {
    "path": "vitest.config.ts",
    "chars": 788,
    "preview": "import { defineConfig } from 'vitest/config';\nimport react from '@vitejs/plugin-react'; // Required for Vitest to proces"
  }
]

// ... and 1 more files (download for full content)

About this extraction

This page contains the full source code of the brandonmcconnell/render-hooks GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 26 files (101.7 KB), approximately 25.9k tokens, and a symbol index with 32 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!