[
  {
    "path": ".codesandbox/ci.json",
    "content": "{\n  \"sandboxes\": [\"swr-basic-p7dg6\", \"swr-states-4une7\", \"swr-infinite-jb5bm\", \"swr-ssr-j9b2y\"],\n  \"node\": \"18\",\n  \"installCommand\": \"csb:install\",\n  \"buildCommand\": \"csb:build\"\n}\n"
  },
  {
    "path": ".editorconfig",
    "content": "root = true\n\n[*.{js,ts,jsx,tsx}]\nindent_size = 2\nindent_style = space"
  },
  {
    "path": ".github/CODEOWNERS",
    "content": "*    @shuding @huozhi\n"
  },
  {
    "path": ".github/CODE_OF_CONDUCT.md",
    "content": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nIn the interest of fostering an open and welcoming environment, we as\ncontributors and maintainers pledge to make participation in our project and\nour community a harassment-free experience for everyone, regardless of age, body\nsize, disability, ethnicity, sex characteristics, gender identity and expression,\nlevel of experience, education, socio-economic status, nationality, personal\nappearance, race, religion, or sexual identity and orientation.\n\n## Our Standards\n\nExamples of behavior that contributes to creating a positive environment\ninclude:\n\n* Using welcoming and inclusive language\n* Being respectful of differing viewpoints and experiences\n* Gracefully accepting constructive criticism\n* Focusing on what is best for the community\n* Showing empathy towards other community members\n\nExamples of unacceptable behavior by participants include:\n\n* The use of sexualized language or imagery and unwelcome sexual attention or\n advances\n* Trolling, insulting/derogatory comments, and personal or political attacks\n* Public or private harassment\n* Publishing others' private information, such as physical or electronic\n address, without explicit permission\n* Other conduct which could reasonably be considered inappropriate in a\n professional setting\n\n## Our Responsibilities\n\nProject maintainers are responsible for clarifying the standards of acceptable\nbehavior and are expected to take appropriate and fair corrective action in\nresponse to any instances of unacceptable behavior.\n\nProject maintainers have the right and responsibility to remove, edit, or\nreject comments, commits, code, wiki edits, issues, and other contributions\nthat are not aligned to this Code of Conduct, or to ban temporarily or\npermanently any contributor for other behaviors that they deem inappropriate,\nthreatening, offensive, or harmful.\n\n## Scope\n\nThis Code of Conduct applies both within project spaces and in public spaces\nwhen an individual is representing the project or its community. Examples of\nrepresenting a project or community include using an official project e-mail\naddress, posting via an official social media account, or acting as an appointed\nrepresentative at an online or offline event. Representation of a project may be\nfurther defined and clarified by project maintainers.\n\n## Enforcement\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be\nreported by contacting the project team at coc@vercel.com. All\ncomplaints will be reviewed and investigated and will result in a response that\nis deemed necessary and appropriate to the circumstances. The project team is\nobligated to maintain confidentiality concerning the reporter of an incident.\nFurther details of specific enforcement policies may be posted separately.\n\nProject maintainers who do not follow or enforce the Code of Conduct in good\nfaith may face temporary or permanent repercussions as determined by other\nmembers of the project's leadership.\n\n## Attribution\n\nThis Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,\navailable at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html\n\n[homepage]: https://www.contributor-covenant.org\n\nFor answers to common questions about this code of conduct, see\nhttps://www.contributor-covenant.org/faq\n"
  },
  {
    "path": ".github/CONTRIBUTING.md",
    "content": "# SWR Contribution Guidelines\n\nThank you for reading this guide and we appreciate any contribution.\n\n## Ask a Question\n\nYou can use the repository's [Discussions](https://github.com/vercel/swr/discussions) page to ask any questions, post feedback, or share your experience on how you use this library.\n\n## Report a Bug\n\nWhenever you find something which is not working properly, please first search the repository's [Issues](https://github.com/vercel/swr/issues) page and make sure it's not reported by someone else already.\n\nIf not, feel free to open an issue with a detailed description of the problem and the expected behavior. And reproduction (for example a [CodeSandbox](https://codesandbox.io) link) will be extremely helpful.\n\n## Request for a New Feature\n\nFor new features, it would be great to have some discussions from the community before starting working on it. You can either create an issue (if there isn't one) or post a thread on the [Discussions](https://github.com/vercel/swr/discussions) page to describe the feature that you want to have.\n\nIf possible, you can add another additional context like how this feature can be implemented technically, what other alternative solutions we can have, etc.\n\n## Open a PR for Bugfix or Feature\n\n### Local Development with Examples\n\nTo run SWR locally, you can start it with any example in the `examples` folder. You need to set up the example and run the command in the root directory for overriding SWR and its dependencies to local assets.\n\nFirst of all, build SWR assets\n\n```sh\ncorepack enable\ncorepack pnpm install\n\npnpm watch\n```\n\nInstall dependency of the target example, for instance `examples/basic`:\n\n\n```sh\n# by default it will run next dev for the example\npnpm next dev examples/basic\n```\n\nAll examples are built with Next.js, so Next.js commands are all supported:\n\n```sh\n# if you want to build and start\npnpm next build examples/basic\npnpm next start examples/basic\n```\n## Update Documentation\n\nTo update the [SWR Documentation](https://swr.vercel.app), you can contribute to the [website repository](https://github.com/vercel/swr-site).\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug report\nabout: Create a bug report for the SWR library\n---\n\n# Bug report\n\n## Description / Observed Behavior\n\nWhat kind of issues did you encounter with SWR?\n\n## Expected Behavior\n\nHow did you expect SWR to behave here?\n\n## Repro Steps / Code Example\n\nOr share your code snippet or a [CodeSandbox](https://codesandbox.io) link is also appreciated!\n\n## Additional Context\n\nSWR version. \nAdd any other context about the problem here.\n\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "blank_issues_enabled: false\ncontact_links:\n  - name: Ask Question\n    url: https://github.com/vercel/swr/discussions\n    about: Ask questions and discuss with other community members\n"
  },
  {
    "path": ".github/SECURITY.md",
    "content": "# Reporting Security Issues\n\nIf you believe you have found a security vulnerability in SWR, we encourage you to let us know right away.\n\nWe will investigate all legitimate reports and do our best to quickly fix the problem.\n\nEmail `security@vercel.com` to disclose any security vulnerabilities.\n\nhttps://vercel.com/security\n"
  },
  {
    "path": ".github/workflows/install/action.yml",
    "content": "name: 'Install'\ndescription: 'Set up and install dependencies'\nruns:\n  using: composite\n  steps:\n    - name: Setup pnpm\n      uses: pnpm/action-setup@v4\n\n    - name: Lock Corepack version\n      shell: bash\n      run: pnpm i -g corepack@0.31.0\n\n    - name: Use Node.js lts\n      uses: actions/setup-node@v4\n      with:\n        node-version: 22\n        cache: pnpm\n        registry-url: 'https://registry.npmjs.org'\n\n    - name: Install Dependencies\n      shell: bash\n      run: |\n        corepack enable\n        node -v\n        pnpm -v\n        pnpm install\n"
  },
  {
    "path": ".github/workflows/test-canary.yml",
    "content": "name: Test React Canary\n\non:\n  workflow_dispatch:\n  schedule:\n    - cron: '0 0 * * *'\n\njobs:\n  test:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v4\n\n      - name: Install\n        uses: ./.github/workflows/install\n\n      - name: Install Canary\n        run: corepack pnpm upgrade react@canary react-dom@canary use-sync-external-store@canary\n\n      - name: Lint and test\n        env:\n          TEST_REACT_CANARY: 1\n        run: |\n          pnpm clean\n          pnpm build\n          pnpm test\n          pnpm test:build\n          pnpm test-typing\n"
  },
  {
    "path": ".github/workflows/test-legacy-react.yml",
    "content": "name: Test React 17\n\non:\n  push:\n    branches:\n      - main\n    tags:\n      - v*\n  pull_request:\n\njobs:\n  test:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v4\n\n      - name: Install\n        uses: ./.github/workflows/install\n\n      - name: Test\n        env:\n          TEST_REACT_LEGACY: 1\n        run: |\n          pnpm clean\n          pnpm build\n          pnpm test\n          pnpm test:build\n          pnpm test-typing\n"
  },
  {
    "path": ".github/workflows/test-release.yml",
    "content": "name: Test and Release\n\non:\n  push:\n    branches:\n      - main\n    tags:\n      - v*\n  pull_request:\n\njobs:\n  test:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v4\n\n      - name: Install\n        uses: ./.github/workflows/install\n\n      - name: Lint and test\n        run: |\n          pnpm clean\n          pnpm build\n          pnpm run-all-checks\n          npm pack\n          pnpm attw\n          pnpm test\n          pnpm test:build\n          pnpm test-typing\n  e2e:\n    runs-on: ubuntu-latest\n    container:\n      image: mcr.microsoft.com/playwright:v1.57.0-noble\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v4\n\n      - name: Install\n        uses: ./.github/workflows/install\n\n      - name: E2E Tests\n        run: |\n          pnpm clean\n          pnpm build\n          pnpm build:e2e\n          pnpm test:e2e\n      - name: Upload test results\n        if: always()\n        uses: actions/upload-artifact@v4\n        with:\n          name: playwright-report\n          path: playwright-report\n  release:\n    runs-on: ubuntu-latest\n    needs: [\"test\", \"e2e\"]\n    if: startsWith(github.ref, 'refs/tags/v')\n    permissions:\n      id-token: write\n    steps:\n      - name: Check out\n        uses: actions/checkout@v4\n\n      - name: Install\n        uses: ./.github/workflows/install\n\n      - name: Determine tag\n        id: determine_tag\n        run: |\n          echo \"tag=$(echo $GITHUB_REF | grep -Eo 'alpha|beta|canary|rc')\" >> $GITHUB_OUTPUT\n\n      - name: Publish to versioned tag\n        if: steps.determine_tag.outputs.tag != ''\n        run: |\n          echo \"Publishing to ${{ steps.determine_tag.outputs.tag }} tag\"\n          npm publish --access public --no-git-checks --provenance --tag ${{ steps.determine_tag.outputs.tag }}\n        env:\n          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN_ELEVATED }}\n\n      - name: Publish to latest\n        if: steps.determine_tag.outputs.tag == ''\n        run: |\n          echo \"Publishing to latest\"\n          npm publish --access public --no-git-checks --provenance\n        env:\n          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN_ELEVATED }}\n"
  },
  {
    "path": ".github/workflows/trigger-release.yml",
    "content": "on:\n  workflow_dispatch:\n    inputs:\n      releaseType:\n        description: Release stable or beta?\n        required: true\n        type: choice\n        options:\n          - beta\n          - stable\n\n      semverType:\n        description: semver type?\n        type: choice\n        options:\n          - patch\n          - minor\n          - major\n\nname: Trigger Release\n\nenv:\n  SEMVER_TYPE: ${{ github.event.inputs.semverType }}\n  RELEASE_TYPE: ${{ github.event.inputs.releaseType }}\n\njobs:\n  start:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v4\n        with:\n          fetch-depth: 10\n          token: ${{ secrets.RELEASE_BOT_GITHUB_TOKEN }}\n      - name: Install\n        uses: ./.github/workflows/install\n      - name: Test\n        run: |\n          pnpm clean\n          pnpm build\n          pnpm run-all-checks\n          pnpm test:build\n\n      - name: Configure git\n        run: |\n          git config user.name \"vercel-release-bot\"\n          git config user.email \"infra+release@vercel.com\"\n\n      - name: Bump version and tag\n        run: |\n          node ./scripts/bump-next-version.js\n\n      - name: Git push\n        run: |\n          git push origin main\n          git push origin --tags\n"
  },
  {
    "path": ".gitignore",
    "content": "node_modules\ndist\n*.log\n*.tgz\n.env\n.next\n.DS_Store\n.idea\n.vscode\n.eslintcache\nexamples/**/yarn.lock\npackage-lock.json\n*.tsbuildinfo\ncoverage\n.rollup.cache\nplaywright-report\ntest-results"
  },
  {
    "path": ".husky/pre-commit",
    "content": "pnpm lint-staged && pnpm types:check\n"
  },
  {
    "path": ".npmrc",
    "content": "# prevent sub-packages from installing peer-deps (multiple react versions)\nauto-install-peers=false"
  },
  {
    "path": ".swcrc",
    "content": "{\n  \"$schema\": \"https://json.schemastore.org/swcrc\",\n  \"sourceMaps\": true,\n  \"jsc\": {\n    \"parser\": {\n      \"syntax\": \"typescript\",\n      \"tsx\": true\n    },\n    \"transform\": {\n      \"react\": {\n        \"runtime\": \"automatic\"\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2023 Vercel, Inc.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "[![SWR](https://assets.vercel.com/image/upload/v1572289618/swr/banner.png)](https://swr.vercel.app)\n\n<p align=\"center\">\n  <a aria-label=\"Vercel logo\" href=\"https://vercel.com\">\n    <img src=\"https://badgen.net/badge/icon/Made%20by%20Vercel?icon=zeit&label&color=black&labelColor=black\">\n  </a>\n  <br/>\n  <a aria-label=\"NPM version\" href=\"https://www.npmjs.com/package/swr\">\n    <img alt=\"\" src=\"https://badgen.net/npm/v/swr\">\n  </a>\n  <a aria-label=\"Package size\" href=\"https://bundlephobia.com/result?p=swr\">\n    <img alt=\"\" src=\"https://badgen.net/bundlephobia/minzip/swr\">\n  </a>\n  <a aria-label=\"License\" href=\"https://github.com/vercel/swr/blob/main/LICENSE\">\n    <img alt=\"\" src=\"https://badgen.net/npm/license/swr\">\n  </a>\n</p>\n\n## Introduction\n\nSWR is a React Hooks library for data fetching.\n\nThe name “**SWR**” is derived from `stale-while-revalidate`, a cache invalidation strategy popularized by [HTTP RFC 5861](https://tools.ietf.org/html/rfc5861).\n**SWR** first returns the data from cache (stale), then sends the request (revalidate), and finally comes with the up-to-date data again.\n\nWith just one hook, you can significantly simplify the data fetching logic in your project. And it also covered in all aspects of speed, correctness, and stability to help you build better experiences:\n\n- **Fast**, **lightweight** and **reusable** data fetching\n- Transport and protocol agnostic\n- Built-in **cache** and request deduplication\n- **Real-time** experience\n- Revalidation on focus\n- Revalidation on network recovery\n- Polling\n- Pagination and scroll position recovery\n- SSR and SSG\n- Local mutation (Optimistic UI)\n- Built-in smart error retry\n- TypeScript\n- React Suspense\n- React Native\n\n...and a lot more.\n\nWith SWR, components will get **a stream of data updates constantly and automatically**. Thus, the UI will be always **fast** and **reactive**.\n\n---\n\n**View full documentation and examples on [swr.vercel.app](https://swr.vercel.app).**\n\n<br/>\n\n## Quick Start\n\n```js\nimport useSWR from 'swr'\n\nfunction Profile() {\n  const { data, error, isLoading } = useSWR('/api/user', fetcher)\n\n  if (error) return <div>failed to load</div>\n  if (isLoading) return <div>loading...</div>\n  return <div>hello {data.name}!</div>\n}\n```\n\nIn this example, the React Hook `useSWR` accepts a `key` and a `fetcher` function.\nThe `key` is a unique identifier of the request, normally the URL of the API. And the `fetcher` accepts\n`key` as its parameter and returns the data asynchronously.\n\n`useSWR` also returns 3 values: `data`, `isLoading` and `error`. When the request (fetcher) is not yet finished,\n`data` will be `undefined` and `isLoading` will be `true`. When we get a response, it sets `data` and `error` based on the result\nof `fetcher`, `isLoading` to false and rerenders the component.\n\nNote that `fetcher` can be any asynchronous function, you can use your favourite data-fetching\nlibrary to handle that part.\n\n---\n\n**View full documentation and examples on [swr.vercel.app](https://swr.vercel.app).**\n\n<br/>\n\n## Authors\n\nThis library is created by the team behind [Next.js](https://nextjs.org), with contributions from our community:\n\n- Shu Ding ([@shuding\\_](https://x.com/shuding_)) - [Vercel](https://vercel.com)\n- Jiachi Liu ([@huozhi](https://x.com/huozhi)) - [Vercel](https://vercel.com)\n- Guillermo Rauch ([@rauchg](https://x.com/rauchg)) - [Vercel](https://vercel.com)\n- Joe Haddad ([@timer150](https://x.com/timer150)) - [Vercel](https://vercel.com)\n- Paco Coursey ([@pacocoursey](https://x.com/pacocoursey)) - [Vercel](https://vercel.com)\n\n[Contributors](https://github.com/vercel/swr/graphs/contributors)\n\nThanks to Ryan Chen for providing the awesome `swr` npm package name!\n\n<br/>\n\n## License\n\nThe MIT License.\n"
  },
  {
    "path": "_internal/package.json",
    "content": "{\n  \"main\": \"../dist/_internal/index.js\",\n  \"module\": \"../dist/_internal/index.mjs\",\n  \"types\": \"../dist/_internal/index.d.ts\",\n  \"private\": true\n}\n"
  },
  {
    "path": "e2e/site/README.md",
    "content": "This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).\n\n## Getting Started\n\nFirst, run the development server:\n\n```bash\nnpm run dev\n# or\nyarn dev\n# or\npnpm dev\n```\n\nOpen [http://localhost:3000](http://localhost:3000) with your browser to see the result.\n\nYou can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.\n\n[API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.ts`.\n\nThe `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages.\n\nThis project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.\n\n## Learn More\n\nTo learn more about Next.js, take a look at the following resources:\n\n- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.\n- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.\n\nYou can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!\n\n## Deploy on Vercel\n\nThe easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.\n\nCheck out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.\n"
  },
  {
    "path": "e2e/site/app/basic-ssr/block.tsx",
    "content": "'use client'\n\nimport useSWR from 'swr'\nimport { useDebugHistory } from '~/lib/use-debug-history'\n\nexport default function Block() {\n  const { data } = useSWR<string>('/api/data', async (url: string) => {\n    const res = await fetch(url).then(v => v.json())\n    return res.name\n  })\n  const debugRef = useDebugHistory(data, 'history:')\n  return (\n    <>\n      <div ref={debugRef}></div>\n      <div>result:{data || 'undefined'}</div>\n    </>\n  )\n}\n"
  },
  {
    "path": "e2e/site/app/basic-ssr/page.tsx",
    "content": "import Block from './block'\n\nexport default function BasicSSRPage() {\n  return <Block />\n}\n"
  },
  {
    "path": "e2e/site/app/concurrent-transition/page.tsx",
    "content": "'use client'\n\nimport { Suspense } from 'react'\nimport dynamic from 'next/dynamic'\n\nconst TransitionDemo = dynamic(() => import('./transition-demo'), {\n  ssr: false\n})\n\nexport default function ConcurrentTransitionPage() {\n  return (\n    <div style={{ padding: '20px', maxWidth: '800px', margin: '0 auto' }}>\n      <h1>React 19 Concurrent Transition Test</h1>\n      <p>\n        This page tests SWR&apos;s behavior with React 19 concurrent\n        transitions. When using useTransition, SWR should &quot;pause&quot;\n        loading states to provide smooth UX.\n      </p>\n      <Suspense fallback={<div>Loading page...</div>}>\n        <TransitionDemo />\n      </Suspense>\n    </div>\n  )\n}\n"
  },
  {
    "path": "e2e/site/app/concurrent-transition/transition-demo.tsx",
    "content": "'use client'\n\nimport React, { useState, useTransition, Suspense, useCallback } from 'react'\nimport useSWR from 'swr'\n\n// Simulate API data fetching with delay\nconst fetcher = async (key: string): Promise<string> => {\n  // Slightly longer delay to make transition behavior more observable\n  await new Promise(resolve => setTimeout(resolve, 150))\n  return key\n}\n\n// Component that uses SWR with suspense\nfunction DataComponent({ swrKey }: { swrKey: string }) {\n  const { data } = useSWR(swrKey, fetcher, {\n    dedupingInterval: 0,\n    suspense: true,\n    // React 19 improvements for concurrent features\n    keepPreviousData: false\n  })\n\n  return <span data-testid=\"data-content\">data:{data}</span>\n}\n\nexport default function TransitionDemo() {\n  const [isPending, startTransition] = useTransition()\n  const [key, setKey] = useState('initial-key')\n\n  const handleTransition = useCallback(() => {\n    startTransition(() => {\n      setKey('new-key')\n    })\n  }, [])\n\n  return (\n    <div>\n      <h2>React 19 Concurrent Transition Demo</h2>\n      <div\n        onClick={handleTransition}\n        data-testid=\"transition-trigger\"\n        style={{\n          cursor: 'pointer',\n          padding: '20px',\n          border: '1px solid #ccc',\n          borderRadius: '4px',\n          backgroundColor: isPending ? '#f0f0f0' : '#fff'\n        }}\n      >\n        <div data-testid=\"pending-state\">isPending:{isPending ? '1' : '0'}</div>\n        <Suspense\n          fallback={<span data-testid=\"loading-fallback\">loading</span>}\n        >\n          <DataComponent swrKey={key} />\n        </Suspense>\n        <p style={{ fontSize: '12px', color: '#666', marginTop: '10px' }}>\n          Click to test concurrent transition behavior\n        </p>\n      </div>\n    </div>\n  )\n}\n"
  },
  {
    "path": "e2e/site/app/issue-2702/page.tsx",
    "content": "import Comp from './reproduction'\n\nexport default function Page() {\n  return (\n    <div>\n      <Comp></Comp>\n    </div>\n  )\n}\n"
  },
  {
    "path": "e2e/site/app/issue-2702/reproduction.tsx",
    "content": "'use client'\nimport useSWR, { preload } from 'swr'\nimport { Suspense, use, useEffect, useState } from 'react'\n\nconst sleep = (time: number, data: string) =>\n  new Promise<string>(resolve => {\n    setTimeout(() => resolve(data), time)\n  })\n\nconst Bug = () => {\n  const a = use(preload('a', () => sleep(1000, 'a')))\n  const { data: b } = useSWR('b', () => sleep(2000, 'b'), {\n    suspense: true\n  })\n  useState(b)\n  return (\n    <div>\n      {a},{b}\n    </div>\n  )\n}\n\nconst Comp = () => {\n  const [loading, setLoading] = useState(true)\n\n  // To prevent SSR\n  useEffect(() => {\n    setLoading(false)\n  }, [])\n\n  if (loading) {\n    return <span>Loading...</span>\n  }\n  return (\n    <Suspense fallback={<div>fetching</div>}>\n      <Bug></Bug>\n    </Suspense>\n  )\n}\n\nexport default Comp\n"
  },
  {
    "path": "e2e/site/app/layout.tsx",
    "content": "export default function RootLayout({\n  children\n}: {\n  children: React.ReactNode\n}) {\n  return (\n    <html lang=\"en\">\n      {/*\n        <head /> will contain the components returned by the nearest parent\n        head.tsx. Find out more at https://beta.nextjs.org/docs/api-reference/file-conventions/head\n      */}\n      <head />\n      <body>{children}</body>\n    </html>\n  )\n}\n"
  },
  {
    "path": "e2e/site/app/mutate-server-action/action.tsx",
    "content": "'use server'\n\nexport async function action(): Promise<{ result: number }> {\n  await sleep(500)\n  return { result: 10086 }\n}\n\nfunction sleep(ms: number): Promise<void> {\n  return new Promise(resolve => {\n    setTimeout(() => {\n      resolve()\n    }, ms)\n  })\n}\n"
  },
  {
    "path": "e2e/site/app/mutate-server-action/page.tsx",
    "content": "'use client'\nimport useSWRMutation from 'swr/mutation'\nimport { action } from './action'\n\nconst useServerActionMutation = () =>\n  useSWRMutation('/api/mutate-server-action', () => action())\n\nconst Page = () => {\n  const { trigger, data, isMutating } = useServerActionMutation()\n  return (\n    <div>\n      <button onClick={() => trigger()}>mutate</button>\n      <div>isMutating: {isMutating.toString()}</div>\n      <div>data: {data?.result}</div>\n    </div>\n  )\n}\n\nexport default Page\n"
  },
  {
    "path": "e2e/site/app/page.tsx",
    "content": "'use client'\n\nimport { Suspense, useReducer } from 'react'\nimport useSWR from 'swr'\nimport { OnlyRenderInClient } from '~/component/only-render-in-client'\n\nconst fetcher = async (key: string) => {\n  // Add a small delay to simulate network request\n  await new Promise(resolve => setTimeout(resolve, 100))\n  return 'SWR'\n}\n\nconst Section = ({ trigger }: { trigger: boolean }) => {\n  const { data } = useSWR(trigger ? 'test-key' : undefined, fetcher, {\n    suspense: true\n  })\n  return <div>{data || 'empty'}</div>\n}\n\nexport default function Page() {\n  const [trigger, toggle] = useReducer(x => !x, false)\n\n  return (\n    <OnlyRenderInClient>\n      <button onClick={toggle}>toggle</button>\n      <Suspense fallback={<div>fallback</div>}>\n        <Section trigger={trigger} />\n      </Suspense>\n    </OnlyRenderInClient>\n  )\n}\n"
  },
  {
    "path": "e2e/site/app/partially-hydrate/layout.tsx",
    "content": "'use client'\nimport type { PropsWithChildren } from 'react'\nimport { useDebugHistory } from '~/lib/use-debug-history'\nimport useData from './use-data'\n\nexport default function Layout({ children }: PropsWithChildren) {\n  const { data } = useData()\n  const debugRef = useDebugHistory(data, 'first history:')\n  return (\n    <html>\n      <head />\n      <body>\n        <div>\n          <div ref={debugRef}></div>\n          <>first data:{data || 'undefined'}</>\n        </div>\n        {children}\n      </body>\n    </html>\n  )\n}\n"
  },
  {
    "path": "e2e/site/app/partially-hydrate/loading.tsx",
    "content": "export default function Loading() {\n  return <div>Loading...</div>\n}\n"
  },
  {
    "path": "e2e/site/app/partially-hydrate/page.tsx",
    "content": "'use client'\nimport { useDebugHistory } from '~/lib/use-debug-history'\nimport useData from './use-data'\nimport { use } from 'react'\n\nlet resolved = false\nconst susp = new Promise(res => {\n  setTimeout(() => {\n    resolved = true\n    res(true)\n  }, 2000)\n})\n\nexport default function Page() {\n  // We trigger the suspense boundary here!\n  if (!resolved) {\n    use(susp)\n  }\n\n  const { data } = useData()\n  const debugRef = useDebugHistory(data, 'second history:')\n  return (\n    <div>\n      <div ref={debugRef}></div>\n      <>second data (delayed hydration):{data || 'undefined'}</>\n    </div>\n  )\n}\n"
  },
  {
    "path": "e2e/site/app/partially-hydrate/use-data.tsx",
    "content": "import useSWR from 'swr'\n\nexport default function useData() {\n  return useSWR<string>('/api/data', async (url: string) => {\n    const res = await fetch(url).then(v => v.json())\n    return res.name\n  })\n}\n"
  },
  {
    "path": "e2e/site/app/perf/page.tsx",
    "content": "'use client'\nimport { useState } from 'react'\nimport useSWR from 'swr'\n\nconst elementCount = 10_000\nconst useData = () => {\n  return useSWR('1', async (url: string) => {\n    return 1\n  })\n}\n\nconst HookUser = () => {\n  const { data } = useData()\n  return <div>{data}</div>\n}\n/**\n * This renders 10,000 divs and is used to compare against the render performance\n * when using swr.\n */\nconst CheapComponent = () => {\n  const cheapComponents = Array.from({ length: elementCount }, (_, i) => (\n    <div key={i}>{i}</div>\n  ))\n  return (\n    <div>\n      <h2>Cheap Component</h2>\n      {cheapComponents}\n    </div>\n  )\n}\n\n/**\n * This renders 10,000 divs, each of which uses the same swr hook.\n */\nconst ExpensiveComponent = () => {\n  const hookComponents = Array.from({ length: elementCount }, (_, i) => (\n    <HookUser key={i} />\n  ))\n  return (\n    <div>\n      <h2>Expensive Component</h2>\n      {hookComponents}\n    </div>\n  )\n}\n\nexport default function PerformancePage() {\n  const [renderExpensive, setRenderExpensive] = useState(false)\n  return (\n    <div>\n      <h1>Performance Page</h1>\n      <label>\n        <input\n          type=\"checkbox\"\n          checked={renderExpensive}\n          onChange={e => setRenderExpensive(e.target.checked)}\n        />\n        Render with swr\n      </label>\n      {!renderExpensive ? <CheapComponent /> : <ExpensiveComponent />}\n    </div>\n  )\n}\n"
  },
  {
    "path": "e2e/site/app/react-server-entry/page.tsx",
    "content": "import { unstable_serialize } from 'swr'\nimport { unstable_serialize as infinite_unstable_serialize } from 'swr/infinite'\n\nexport default function Page() {\n  return (\n    <>\n      <div>SWR Server Component entry test</div>\n      <div>unstable_serialize: {unstable_serialize('useSWR')}</div>\n      <div>\n        infinite_unstable_serialize:{' '}\n        {infinite_unstable_serialize(() => 'useSWRInfinite')}\n      </div>\n    </>\n  )\n}\n"
  },
  {
    "path": "e2e/site/app/render-count/page.tsx",
    "content": "'use client'\nimport useSWR from 'swr'\n\nexport default function Page() {\n  useSWR('swr should not cause extra rerenders')\n  console.count('render')\n  return <div>render count pages</div>\n}\n"
  },
  {
    "path": "e2e/site/app/render-preload-avoid-waterfall/page.tsx",
    "content": "'use client'\n\nimport { Suspense, useEffect, useState } from 'react'\nimport useSWR, { preload } from 'swr'\nimport { OnlyRenderInClient } from '~/component/only-render-in-client'\nimport { sleep } from '~/lib/sleep'\n\nconst keyA = 'render-preload-avoid-waterfall:a'\nconst keyB = 'render-preload-avoid-waterfall:b'\nconst delay = 200\n\nasync function fetcherA() {\n  await sleep(delay)\n  return 'foo'\n}\n\nasync function fetcherB() {\n  await sleep(delay)\n  return 'bar'\n}\n\nfunction Preload({ children }: { children?: React.ReactNode }) {\n  const [ready, setReady] = useState(false)\n\n  useEffect(() => {\n    preload(keyA, fetcherA)\n    preload(keyB, fetcherB)\n    setReady(true)\n  }, [])\n\n  return ready ? <>{children}</> : null\n}\n\nfunction Content() {\n  const { data: first } = useSWR(keyA, fetcherA, { suspense: true })\n  const { data: second } = useSWR(keyB, fetcherB, { suspense: true })\n\n  useEffect(() => {\n    if (!first || !second) {\n      return\n    }\n  }, [first, second])\n\n  return (\n    <div style={{ display: 'grid', gap: '0.5rem' }}>\n      <div data-testid=\"data\">\n        data:{first}:{second}\n      </div>\n    </div>\n  )\n}\n\nexport default function Page() {\n  return (\n    <OnlyRenderInClient>\n      <Preload>\n        <Suspense fallback={<div data-testid=\"fallback\">Loading...</div>}>\n          <Content />\n        </Suspense>\n      </Preload>\n    </OnlyRenderInClient>\n  )\n}\n"
  },
  {
    "path": "e2e/site/app/render-preload-basic/page.tsx",
    "content": "'use client'\n\nimport { Suspense, useEffect, useState } from 'react'\nimport useSWR, { preload } from 'swr'\nimport { OnlyRenderInClient } from '~/component/only-render-in-client'\nimport { sleep } from '~/lib/sleep'\n\nconst key = 'render-preload-basic'\nlet fetchCount = 0\n\nasync function fetcher() {\n  await sleep(100)\n  fetchCount += 1\n  return 'foo'\n}\n\nfunction Preload({ children }: { children?: React.ReactNode }) {\n  const [isPreloaded, setIsPreloaded] = useState(false)\n\n  useEffect(() => {\n    preload(key, fetcher)\n    setIsPreloaded(true)\n  }, [])\n  return <>{isPreloaded ? children : null}</>\n}\n\nexport default function Page() {\n  const { data } = useSWR(key, fetcher)\n  const [count, setCount] = useState(fetchCount)\n\n  useEffect(() => {\n    setCount(fetchCount)\n  }, [data])\n\n  return (\n    <OnlyRenderInClient>\n      <Preload>\n        <Suspense fallback={<div>Loading...</div>}>\n          <div style={{ display: 'grid', gap: '0.5rem' }}>\n            <div data-testid=\"data\">data:{data ?? ''}</div>\n            <div data-testid=\"fetch-count\">fetches: {count}</div>\n          </div>\n        </Suspense>\n      </Preload>\n    </OnlyRenderInClient>\n  )\n}\n"
  },
  {
    "path": "e2e/site/app/render-promise-suspense-error/page.tsx",
    "content": "'use client'\n\nimport { Suspense, useMemo, useState } from 'react'\nimport type { ReactNode } from 'react'\nimport { ErrorBoundary } from 'react-error-boundary'\nimport useSWR, { SWRConfig } from 'swr'\nimport { OnlyRenderInClient } from '~/component/only-render-in-client'\nimport { sleep } from '~/lib/sleep'\n\nconst key = 'render-promise-suspense-error'\nconst fallbackDelay = 150\n\nfunction PromiseConfig({ children }: { children: ReactNode }) {\n  const [fallback] = useState(() =>\n    sleep(fallbackDelay).then(() => {\n      throw new Error('error')\n    })\n  )\n\n  const value = useMemo(() => ({ fallback: { [key]: fallback } }), [fallback])\n\n  return <SWRConfig value={value}>{children}</SWRConfig>\n}\n\nfunction Content() {\n  const { data } = useSWR<string>(key)\n  return <div data-testid=\"data\">data:{data ?? 'undefined'}</div>\n}\n\nexport default function Page() {\n  return (\n    <OnlyRenderInClient>\n      <PromiseConfig>\n        <ErrorBoundary\n          fallbackRender={({ error }) => (\n            <div data-testid=\"error\">{error.message}</div>\n          )}\n        >\n          <Suspense fallback={<div data-testid=\"fallback\">loading</div>}>\n            <Content />\n          </Suspense>\n        </ErrorBoundary>\n      </PromiseConfig>\n    </OnlyRenderInClient>\n  )\n}\n"
  },
  {
    "path": "e2e/site/app/render-promise-suspense-resolve/page.tsx",
    "content": "'use client'\n\nimport { Suspense, useMemo, useState } from 'react'\nimport type { ReactNode } from 'react'\nimport useSWR, { SWRConfig } from 'swr'\nimport { OnlyRenderInClient } from '~/component/only-render-in-client'\nimport { sleep } from '~/lib/sleep'\nimport { useDebugHistory } from '~/lib/use-debug-history'\n\nconst key = 'render-promise-suspense-resolve'\nconst fallbackDelay = 150\nconst fetchDelay = 200\n\nasync function fetcher() {\n  await sleep(fetchDelay)\n  return 'new data'\n}\n\nfunction PromiseConfig({ children }: { children: ReactNode }) {\n  const [fallback] = useState(() =>\n    sleep(fallbackDelay).then(() => 'initial data')\n  )\n\n  const value = useMemo(() => ({ fallback: { [key]: fallback } }), [fallback])\n\n  return <SWRConfig value={value}>{children}</SWRConfig>\n}\n\nfunction Content() {\n  const { data } = useSWR(key, fetcher)\n  const historyRef = useDebugHistory(data, 'history:')\n\n  return (\n    <div style={{ display: 'grid', gap: '0.5rem' }}>\n      <div data-testid=\"data\">data:{data ?? 'undefined'}</div>\n      <div data-testid=\"history\" ref={historyRef}></div>\n    </div>\n  )\n}\n\nexport default function Page() {\n  return (\n    <OnlyRenderInClient>\n      <PromiseConfig>\n        <Suspense fallback={<div data-testid=\"fallback\">loading</div>}>\n          <Content />\n        </Suspense>\n      </PromiseConfig>\n    </OnlyRenderInClient>\n  )\n}\n"
  },
  {
    "path": "e2e/site/app/render-promise-suspense-shared/page.tsx",
    "content": "'use client'\n\nimport { Suspense, useMemo, useState } from 'react'\nimport type { ReactNode } from 'react'\nimport useSWR, { SWRConfig } from 'swr'\nimport { OnlyRenderInClient } from '~/component/only-render-in-client'\nimport { sleep } from '~/lib/sleep'\n\nconst key = 'render-promise-suspense-shared'\nconst fallbackDelay = 150\n\nfunction PromiseConfig({ children }: { children: ReactNode }) {\n  const [fallback] = useState(() => sleep(fallbackDelay).then(() => 'value'))\n  const value = useMemo(() => ({ fallback: { [key]: fallback } }), [fallback])\n  return <SWRConfig value={value}>{children}</SWRConfig>\n}\n\nfunction Item({ id }: { id: string }) {\n  const { data } = useSWR<string>(key)\n  return <div data-testid={`data-${id}`}>data:{data ?? 'undefined'}</div>\n}\n\nexport default function Page() {\n  return (\n    <OnlyRenderInClient>\n      <PromiseConfig>\n        <Suspense fallback={<div data-testid=\"fallback\">loading</div>}>\n          <div style={{ display: 'grid', gap: '0.5rem' }}>\n            <Item id=\"first\" />\n            <Item id=\"second\" />\n          </div>\n        </Suspense>\n      </PromiseConfig>\n    </OnlyRenderInClient>\n  )\n}\n"
  },
  {
    "path": "e2e/site/app/render-suspense-avoid-rerender/page.tsx",
    "content": "'use client'\n\nimport { Suspense, useRef } from 'react'\nimport useSWR from 'swr'\nimport { OnlyRenderInClient } from '~/component/only-render-in-client'\nimport { sleep } from '~/lib/sleep'\n\nasync function fetchValue() {\n  await sleep(150)\n  return 'SWR'\n}\n\nfunction Section() {\n  const startCountRef = useRef(0)\n  const dataCountRef = useRef(0)\n  const prevDataRef = useRef<any>(Symbol('initial'))\n\n  startCountRef.current += 1\n\n  const { data } = useSWR('render-suspense-avoid-rerender', fetchValue, {\n    suspense: true\n  })\n\n  if (data !== prevDataRef.current) {\n    if (data !== undefined) {\n      dataCountRef.current += 1\n    }\n    prevDataRef.current = data\n  }\n\n  return (\n    <div style={{ display: 'grid', gap: '0.5rem' }}>\n      <div data-testid=\"start-count\">\n        start renders: {startCountRef.current}\n      </div>\n      <div data-testid=\"data-count\">data renders: {dataCountRef.current}</div>\n      <div data-testid=\"data\">{data}</div>\n    </div>\n  )\n}\n\nexport default function Page() {\n  return (\n    <OnlyRenderInClient>\n      <Suspense fallback={<div data-testid=\"fallback\">fallback</div>}>\n        <Section />\n      </Suspense>\n    </OnlyRenderInClient>\n  )\n}\n"
  },
  {
    "path": "e2e/site/app/render-suspense-cached-error/page.tsx",
    "content": "'use client'\n\nimport { Suspense } from 'react'\nimport { ErrorBoundary } from 'react-error-boundary'\nimport useSWR, { SWRConfig } from 'swr'\nimport { OnlyRenderInClient } from '~/component/only-render-in-client'\nimport { sleep } from '~/lib/sleep'\n\nasync function fetchWithError(): Promise<string> {\n  await sleep(150)\n  throw new Error('error')\n}\n\nfunction Section() {\n  const { data, error } = useSWR<string>(\n    'render-suspense-cached-error',\n    fetchWithError,\n    { suspense: true }\n  )\n\n  return (\n    <div data-testid=\"result\">\n      data: {data ?? ''}, error: {error?.message ?? ''}\n    </div>\n  )\n}\n\nconst cache = new Map()\ncache.set('render-suspense-cached-error', 'hello')\nconst cacheProvider = () => cache\n\nfunction ErrorFallback(_: { error: Error; resetErrorBoundary: () => void }) {\n  return <div data-testid=\"error\">error boundary</div>\n}\n\nexport default function Page() {\n  return (\n    <OnlyRenderInClient>\n      <SWRConfig\n        value={{\n          provider: cacheProvider\n        }}\n      >\n        <ErrorBoundary FallbackComponent={ErrorFallback}>\n          <Suspense fallback={<div data-testid=\"fallback\">fallback</div>}>\n            <Section />\n          </Suspense>\n        </ErrorBoundary>\n      </SWRConfig>\n    </OnlyRenderInClient>\n  )\n}\n"
  },
  {
    "path": "e2e/site/app/render-suspense-error/page.tsx",
    "content": "'use client'\n\nimport { Suspense } from 'react'\nimport useSWR from 'swr'\nimport { ErrorBoundary } from 'react-error-boundary'\nimport { OnlyRenderInClient } from '~/component/only-render-in-client'\nimport { sleep } from '~/lib/sleep'\n\nasync function fetchWithError(): Promise<string> {\n  await sleep(120)\n  throw new Error('error')\n}\n\nfunction Section() {\n  const { data } = useSWR('render-suspense-error', fetchWithError, {\n    suspense: true\n  })\n\n  return <div data-testid=\"data\">{data}</div>\n}\n\nfunction ErrorFallback(_: { error: Error; resetErrorBoundary: () => void }) {\n  return <div data-testid=\"error\">error boundary</div>\n}\n\nexport default function Page() {\n  return (\n    <OnlyRenderInClient>\n      <ErrorBoundary FallbackComponent={ErrorFallback}>\n        <Suspense fallback={<div data-testid=\"fallback\">fallback</div>}>\n          <Section />\n        </Suspense>\n      </ErrorBoundary>\n    </OnlyRenderInClient>\n  )\n}\n"
  },
  {
    "path": "e2e/site/app/render-suspense-fallback/page.tsx",
    "content": "'use client'\n\nimport { Suspense } from 'react'\nimport useSWR from 'swr'\nimport { OnlyRenderInClient } from '~/component/only-render-in-client'\nimport { sleep } from '~/lib/sleep'\n\nasync function fetchGreeting() {\n  await sleep(500)\n  return 'SWR'\n}\n\nfunction Section() {\n  const { data } = useSWR<string>('render-suspense-fallback', fetchGreeting, {\n    suspense: true\n  })\n\n  return <div data-testid=\"data\">{data}</div>\n}\n\nexport default function Page() {\n  return (\n    <OnlyRenderInClient>\n      <Suspense fallback={<div data-testid=\"fallback\">fallback</div>}>\n        <Section />\n      </Suspense>\n    </OnlyRenderInClient>\n  )\n}\n"
  },
  {
    "path": "e2e/site/app/render-suspense-fetcher/page.tsx",
    "content": "'use client'\n\nimport { Suspense, useRef, useState } from 'react'\nimport useSWR from 'swr'\nimport { OnlyRenderInClient } from '~/component/only-render-in-client'\nimport { sleep } from '~/lib/sleep'\n\nfunction Section() {\n  const [prefix, setPrefix] = useState('a')\n  const fetcherRef = useRef<() => Promise<string>>(() =>\n    sleep(100).then(() => 'foo')\n  )\n\n  const { data } = useSWR(\n    `render-suspense-fetcher-${prefix}`,\n    () => fetcherRef.current(),\n    { suspense: true }\n  )\n\n  return (\n    <div style={{ display: 'grid', gap: '0.75rem' }}>\n      <div data-testid=\"data\">data:{data}</div>\n      <div style={{ display: 'flex', gap: '0.5rem' }}>\n        <button\n          type=\"button\"\n          onClick={() => {\n            fetcherRef.current = () => sleep(100).then(() => 'foo')\n            setPrefix('a')\n          }}\n          data-testid=\"set-foo\"\n        >\n          set foo\n        </button>\n        <button\n          type=\"button\"\n          onClick={() => {\n            fetcherRef.current = () => sleep(100).then(() => 'bar')\n            setPrefix('b')\n          }}\n          data-testid=\"set-bar\"\n        >\n          set bar\n        </button>\n      </div>\n    </div>\n  )\n}\n\nexport default function Page() {\n  return (\n    <OnlyRenderInClient>\n      <Suspense fallback={<div data-testid=\"fallback\">loading</div>}>\n        <Section />\n      </Suspense>\n    </OnlyRenderInClient>\n  )\n}\n"
  },
  {
    "path": "e2e/site/app/render-suspense-infinite-preload/page.tsx",
    "content": "'use client'\n\nimport { Suspense, useEffect, useState } from 'react'\nimport useSWRInfinite from 'swr/infinite'\nimport { preload } from 'swr'\nimport { OnlyRenderInClient } from '~/component/only-render-in-client'\nimport { sleep } from '~/lib/sleep'\n\nconst baseKey = 'render-suspense-infinite-preload'\nconst getKey = (index: number) => `${baseKey}-${index}`\n\nlet fetchCount = 0\nlet preloaded = false\nlet fallbackRenderCount = 0\n\nasync function fetcher(key: string) {\n  fetchCount += 1\n  await sleep(100)\n  return `${key}-value`\n}\n\nif (!preloaded) {\n  preloaded = true\n  preload(getKey(0), fetcher)\n}\n\nfunction Section() {\n  const { data } = useSWRInfinite(index => getKey(index), fetcher, {\n    suspense: true\n  })\n\n  const [count, setCount] = useState(fetchCount)\n  const [fallbackCount, setFallbackCount] = useState(fallbackRenderCount)\n  useEffect(() => {\n    setCount(fetchCount)\n    setFallbackCount(fallbackRenderCount)\n  }, [data])\n\n  const firstPage = data?.[0]\n\n  return (\n    <div style={{ display: 'grid', gap: '0.5rem' }}>\n      <div data-testid=\"data\">{firstPage ?? 'no-data'}</div>\n      <div data-testid=\"fetch-count\">fetches: {count}</div>\n      <div data-testid=\"fallback-count\">fallback renders: {fallbackCount}</div>\n    </div>\n  )\n}\n\nfunction Fallback() {\n  fallbackRenderCount += 1\n  return <div data-testid=\"fallback\">loading</div>\n}\n\nexport default function Page() {\n  return (\n    <OnlyRenderInClient>\n      <Suspense fallback={<Fallback />}>\n        <Section />\n      </Suspense>\n    </OnlyRenderInClient>\n  )\n}\n"
  },
  {
    "path": "e2e/site/app/render-suspense-initial-data/page.tsx",
    "content": "'use client'\n\nimport { Suspense } from 'react'\nimport useSWR from 'swr'\n\nconst fetcher = () => 'SWR'\n\nfunction PageContent() {\n  const { data } = useSWR('render-suspense-initial-data', fetcher, {\n    fallbackData: 'Initial',\n    suspense: true\n  })\n\n  return <div data-testid=\"data\">hello, {data}</div>\n}\n\nexport default function Page() {\n  return (\n    <Suspense fallback={<div data-testid=\"fallback\">fallback</div>}>\n      <PageContent />\n    </Suspense>\n  )\n}\n"
  },
  {
    "path": "e2e/site/app/render-suspense-keep-previous/page.tsx",
    "content": "'use client'\n\nimport { Suspense, useState } from 'react'\nimport useSWR from 'swr'\nimport { OnlyRenderInClient } from '~/component/only-render-in-client'\nimport { sleep } from '~/lib/sleep'\n\nasync function fetchValue(key: string) {\n  await sleep(150)\n  return `data:${key}`\n}\n\nfunction Section() {\n  const [query, setQuery] = useState('origin')\n  const { data } = useSWR(query, fetchValue, {\n    suspense: true,\n    keepPreviousData: true\n  })\n\n  return (\n    <div style={{ display: 'grid', gap: '0.5rem' }}>\n      <div data-testid=\"data\">{data}</div>\n      <div style={{ display: 'flex', gap: '0.5rem' }}>\n        <button\n          type=\"button\"\n          onClick={() => setQuery('origin')}\n          data-testid=\"set-origin\"\n        >\n          origin\n        </button>\n        <button\n          type=\"button\"\n          onClick={() => setQuery('next')}\n          data-testid=\"set-next\"\n        >\n          next\n        </button>\n      </div>\n    </div>\n  )\n}\n\nfunction Fallback() {\n  return <div data-testid=\"fallback\">loading</div>\n}\n\nexport default function Page() {\n  return (\n    <OnlyRenderInClient>\n      <Suspense fallback={<Fallback />}>\n        <Section />\n      </Suspense>\n    </OnlyRenderInClient>\n  )\n}\n"
  },
  {
    "path": "e2e/site/app/render-suspense-key-change/page.tsx",
    "content": "'use client'\n\nimport { Suspense, useState } from 'react'\nimport useSWR from 'swr'\nimport { OnlyRenderInClient } from '~/component/only-render-in-client'\nimport { sleep } from '~/lib/sleep'\n\nasync function fetchValue(key: string) {\n  await sleep(120)\n  return key\n}\n\nfunction Section() {\n  const [key, setKey] = useState('initial')\n  const { data } = useSWR(\n    key ? `render-suspense-key-change-${key}` : null,\n    fetchValue,\n    { suspense: true }\n  )\n\n  return (\n    <div>\n      <div data-testid=\"data\">data: {data}</div>\n      <button\n        type=\"button\"\n        onClick={() => setKey('updated')}\n        data-testid=\"toggle\"\n      >\n        change\n      </button>\n    </div>\n  )\n}\n\nexport default function Page() {\n  return (\n    <OnlyRenderInClient>\n      <Suspense fallback={<div data-testid=\"fallback\">fallback</div>}>\n        <Section />\n      </Suspense>\n    </OnlyRenderInClient>\n  )\n}\n"
  },
  {
    "path": "e2e/site/app/render-suspense-multiple-fallbacks/page.tsx",
    "content": "'use client'\n\nimport { Suspense } from 'react'\nimport useSWR from 'swr'\nimport { OnlyRenderInClient } from '~/component/only-render-in-client'\nimport { sleep } from '~/lib/sleep'\n\nasync function fetchValue(key: string) {\n  if (key === 'render-suspense-multiple-fallbacks-1') {\n    await sleep(50)\n    return 1\n  }\n  await sleep(140)\n  return 2\n}\n\nfunction Section() {\n  const { data: v1 } = useSWR<number>(\n    'render-suspense-multiple-fallbacks-1',\n    fetchValue,\n    { suspense: true }\n  )\n  const { data: v2 } = useSWR<number>(\n    'render-suspense-multiple-fallbacks-2',\n    fetchValue,\n    { suspense: true }\n  )\n\n  return <div data-testid=\"data\">{(v1 ?? 0) + (v2 ?? 0)}</div>\n}\n\nexport default function Page() {\n  return (\n    <OnlyRenderInClient>\n      <Suspense fallback={<div data-testid=\"fallback\">fallback</div>}>\n        <Section />\n      </Suspense>\n    </OnlyRenderInClient>\n  )\n}\n"
  },
  {
    "path": "e2e/site/app/render-suspense-no-revalidate/page.tsx",
    "content": "'use client'\n\nimport { Suspense, useLayoutEffect, useState } from 'react'\nimport useSWR, { preload } from 'swr'\nimport { OnlyRenderInClient } from '~/component/only-render-in-client'\nimport { sleep } from '~/lib/sleep'\n\nasync function fetchFreshValue() {\n  await sleep(120)\n  return 'fresh'\n}\n\nfunction Preload({ children }: { children?: React.ReactNode }) {\n  const [isMounted, setIsMounted] = useState(false)\n  useLayoutEffect(() => {\n    preload('render-suspense-no-revalidate', () => 'cached')\n    setIsMounted(true)\n  }, [])\n  return isMounted ? <>{children}</> : null\n}\n\nfunction Section() {\n  const { data } = useSWR('render-suspense-no-revalidate', fetchFreshValue, {\n    suspense: true,\n    revalidateIfStale: false\n  })\n\n  return <div data-testid=\"data\">data: {data}</div>\n}\n\nexport default function Page() {\n  return (\n    <OnlyRenderInClient>\n      <Preload>\n        <Suspense fallback={<div data-testid=\"fallback\">fallback</div>}>\n          <Section />\n        </Suspense>\n      </Preload>\n    </OnlyRenderInClient>\n  )\n}\n"
  },
  {
    "path": "e2e/site/app/render-suspense-non-promise/page.tsx",
    "content": "'use client'\n\nimport { Suspense } from 'react'\nimport useSWR from 'swr'\nimport { OnlyRenderInClient } from '~/component/only-render-in-client'\n\nfunction Section() {\n  const { data } = useSWR('render-suspense-non-promise', () => 'hello', {\n    suspense: true\n  })\n\n  return <div data-testid=\"data\">{data}</div>\n}\n\nexport default function Page() {\n  return (\n    <OnlyRenderInClient>\n      <Suspense fallback={<div data-testid=\"fallback\">fallback</div>}>\n        <Section />\n      </Suspense>\n    </OnlyRenderInClient>\n  )\n}\n"
  },
  {
    "path": "e2e/site/app/render-suspense-null-key/page.tsx",
    "content": "'use client'\n\nimport { Suspense, useState } from 'react'\nimport useSWR from 'swr'\nimport { OnlyRenderInClient } from '~/component/only-render-in-client'\nimport { sleep } from '~/lib/sleep'\n\nasync function fetchValue(key: string) {\n  await sleep(150)\n  return key\n}\n\nfunction Result({ query }: { query: string }) {\n  const { data } = useSWR(\n    query ? `render-suspense-null-key-${query}` : null,\n    fetchValue,\n    {\n      suspense: true\n    }\n  )\n\n  const text = data ?? 'render-suspense-null-key-nodata'\n\n  return <div data-testid=\"data\">{text}</div>\n}\n\nexport default function Page() {\n  const [query, setQuery] = useState('123')\n\n  return (\n    <OnlyRenderInClient>\n      <div style={{ display: 'grid', gap: '0.5rem' }}>\n        <div style={{ display: 'flex', gap: '0.5rem' }}>\n          <button\n            type=\"button\"\n            onClick={() => setQuery('123')}\n            data-testid=\"set-123\"\n          >\n            set 123\n          </button>\n          <button\n            type=\"button\"\n            onClick={() => setQuery('')}\n            data-testid=\"set-empty\"\n          >\n            clear\n          </button>\n          <button\n            type=\"button\"\n            onClick={() => setQuery('456')}\n            data-testid=\"set-456\"\n          >\n            set 456\n          </button>\n        </div>\n        <Suspense fallback={null}>\n          <Result query={query} />\n        </Suspense>\n      </div>\n    </OnlyRenderInClient>\n  )\n}\n"
  },
  {
    "path": "e2e/site/app/render-suspense-same-data/page.tsx",
    "content": "'use client'\n\nimport { Suspense, useState } from 'react'\nimport useSWR from 'swr'\nimport { OnlyRenderInClient } from '~/component/only-render-in-client'\nimport { sleep } from '~/lib/sleep'\n\nasync function fetchValue() {\n  await sleep(120)\n  return '123'\n}\n\nfunction Section() {\n  const [step, setStep] = useState(1)\n  const { data } = useSWR(`render-suspense-same-data-${step}`, fetchValue, {\n    suspense: true\n  })\n\n  return (\n    <div>\n      <div data-testid=\"data\">\n        data: {data},{step}\n      </div>\n      <button\n        type=\"button\"\n        data-testid=\"increment\"\n        onClick={() => setStep(current => current + 1)}\n      >\n        next\n      </button>\n    </div>\n  )\n}\n\nexport default function Page() {\n  return (\n    <OnlyRenderInClient>\n      <Suspense fallback={<div data-testid=\"fallback\">fallback</div>}>\n        <Section />\n      </Suspense>\n    </OnlyRenderInClient>\n  )\n}\n"
  },
  {
    "path": "e2e/site/app/server-prefetch-warning/page.tsx",
    "content": "'use client'\n\nimport { useEffect, useState } from 'react'\nimport useSWR, { SWRConfig } from 'swr'\n\nconst fetcher = () => 'SWR'\n\nfunction Content() {\n  const [hydrated, setHydrated] = useState(false)\n  useEffect(() => {\n    setHydrated(true)\n  }, [])\n\n  useSWR('ssr:1', fetcher)\n  useSWR('ssr:2', fetcher)\n  useSWR('ssr:3', fetcher, { strictServerPrefetchWarning: false })\n  useSWR('ssr:4', fetcher, { fallbackData: 'SWR' })\n  useSWR('ssr:5', fetcher)\n\n  return (\n    <div style={{ display: 'grid', gap: '0.5rem' }}>\n      <div data-testid=\"title\">server-prefetch-warning</div>\n      <div data-testid=\"hydration-state\">{hydrated ? 'hydrated' : 'ssr'}</div>\n    </div>\n  )\n}\n\nexport default function ServerPrefetchWarningPage() {\n  return (\n    <SWRConfig\n      value={{\n        strictServerPrefetchWarning: true,\n        fallback: {\n          'ssr:5': 'SWR'\n        }\n      }}\n    >\n      <Content />\n    </SWRConfig>\n  )\n}\n"
  },
  {
    "path": "e2e/site/app/suspense-after-preload/page.tsx",
    "content": "'use client'\n\nimport { Suspense } from 'react'\nimport dynamic from 'next/dynamic'\n\nconst RemoteData = dynamic(() => import('./remote-data'), {\n  ssr: false\n})\n\nexport default function HomePage() {\n  return (\n    <Suspense fallback={<div>loading component</div>}>\n      <RemoteData></RemoteData>\n    </Suspense>\n  )\n}\n"
  },
  {
    "path": "e2e/site/app/suspense-after-preload/remote-data.tsx",
    "content": "'use client'\nimport { Suspense, useState } from 'react'\nimport useSWR from 'swr'\nimport { preload } from 'swr'\n\nconst fetcher = ([key, delay]: [key: string, delay: number]) =>\n  new Promise<string>(r => {\n    setTimeout(r, delay, key)\n  })\n\nconst key = ['suspense-after-preload', 300] as const\nconst useRemoteData = () =>\n  useSWR(key, fetcher, {\n    suspense: true\n  })\n\nconst Demo = () => {\n  const { data } = useRemoteData()\n  return <div>{data}</div>\n}\n\nfunction Comp() {\n  const [show, toggle] = useState(false)\n\n  return (\n    <div className=\"App\">\n      <button\n        onClick={async () => {\n          preload(key, fetcher)\n          toggle(!show)\n        }}\n      >\n        preload\n      </button>\n      {show ? (\n        <Suspense fallback={<div>loading</div>}>\n          <Demo />\n        </Suspense>\n      ) : null}\n    </div>\n  )\n}\n\nexport default Comp\n"
  },
  {
    "path": "e2e/site/app/suspense-fallback/layout.tsx",
    "content": "import { SWRConfig } from 'swr'\n\nfunction createPromiseData(data: any, timeout: number) {\n  return new Promise(resolve => {\n    setTimeout(() => {\n      resolve(data)\n    }, timeout)\n  })\n}\n\nexport default function Layout({ children }: { children: React.ReactNode }) {\n  const fallback = {\n    '/api/promise': createPromiseData({ value: 'async promise' }, 2000)\n  }\n\n  return <SWRConfig value={{ fallback }}>{children}</SWRConfig>\n}\n"
  },
  {
    "path": "e2e/site/app/suspense-fallback/promise/page.tsx",
    "content": "'use client'\n\nimport useSWR from 'swr'\n\nexport default function Page() {\n  const { data, isLoading } = useSWR('/api/promise')\n\n  return <div>{isLoading ? 'loading...' : data?.value}</div>\n}\n"
  },
  {
    "path": "e2e/site/app/suspense-infinite-get-key/page.tsx",
    "content": "'use client'\n\nimport { Suspense, useState } from 'react'\nimport useSWRInfinite from 'swr/infinite'\nimport { OnlyRenderInClient } from '~/component/only-render-in-client'\nimport { sleep } from '~/lib/sleep'\n\nconst DATA: Record<string, string[]> = {\n  'suspense-key-a': ['A1', 'A2', 'A3'],\n  'suspense-key-b': ['B1', 'B2', 'B3']\n}\n\nasync function fetchList(key: string) {\n  await sleep(150)\n  return DATA[key]\n}\n\nfunction List() {\n  const [status, setStatus] = useState<'a' | 'b'>('a')\n  const { data, setSize } = useSWRInfinite(\n    () => (status === 'a' ? 'suspense-key-a' : 'suspense-key-b'),\n    fetchList,\n    { suspense: true }\n  )\n\n  return (\n    <>\n      <div data-testid=\"data\">data: {String(data)}</div>\n      <button\n        type=\"button\"\n        onClick={() => {\n          setStatus('b')\n          void setSize(1)\n        }}\n      >\n        mutate\n      </button>\n    </>\n  )\n}\n\nexport default function Page() {\n  return (\n    <OnlyRenderInClient>\n      <Suspense fallback={<div>loading</div>}>\n        <List />\n      </Suspense>\n    </OnlyRenderInClient>\n  )\n}\n"
  },
  {
    "path": "e2e/site/app/suspense-retry/manual-retry.tsx",
    "content": "'use client'\nimport { Suspense } from 'react'\nimport { ErrorBoundary } from 'react-error-boundary'\nimport { useRemoteData, preloadRemote } from './use-remote-data'\n\nconst Demo = () => {\n  const { data } = useRemoteData()\n  return <div>data: {data}</div>\n}\n\nfunction Fallback({ resetErrorBoundary }: any) {\n  return (\n    <div role=\"alert\">\n      <p>Something went wrong</p>\n      <button\n        onClick={() => {\n          resetErrorBoundary()\n        }}\n      >\n        retry\n      </button>\n    </div>\n  )\n}\n\nfunction RemoteData() {\n  return (\n    <div className=\"App\">\n      <ErrorBoundary\n        FallbackComponent={Fallback}\n        onReset={() => {\n          preloadRemote()\n        }}\n      >\n        <Suspense fallback={<div>loading</div>}>\n          <Demo />\n        </Suspense>\n      </ErrorBoundary>\n    </div>\n  )\n}\n\nexport default RemoteData\n"
  },
  {
    "path": "e2e/site/app/suspense-retry/page.tsx",
    "content": "'use client'\n\nimport { Suspense } from 'react'\nimport dynamic from 'next/dynamic'\n\nconst RemoteData = dynamic(() => import('./manual-retry'), {\n  ssr: false\n})\n\nexport default function HomePage() {\n  return (\n    <Suspense fallback={<div>loading component</div>}>\n      <RemoteData></RemoteData>\n    </Suspense>\n  )\n}\n"
  },
  {
    "path": "e2e/site/app/suspense-retry/use-remote-data.ts",
    "content": "'use client'\nimport useSWR from 'swr'\nimport { preload } from 'swr'\n\nlet count = 0\nconst fetcher = () => {\n  count++\n  if (count === 1) return Promise.reject('wrong')\n  return fetch('/api/retry')\n    .then(r => r.json())\n    .then(r => r.name)\n}\n\nconst key = 'manual-retry-18-3'\n\nexport const useRemoteData = () =>\n  useSWR(key, fetcher, {\n    suspense: true\n  })\n\nexport const preloadRemote = () => preload(key, fetcher)\n"
  },
  {
    "path": "e2e/site/app/suspense-undefined-key/page.tsx",
    "content": "'use client'\n\nimport { Suspense, useReducer } from 'react'\nimport useSWR from 'swr'\n\nconst fetcher = async (key: string) => {\n  // Add a small delay to simulate network request\n  await new Promise(resolve => setTimeout(resolve, 100))\n  return 'SWR'\n}\n\nconst Section = ({ trigger }: { trigger: boolean }) => {\n  const { data } = useSWR(trigger ? 'test-key' : undefined, fetcher, {\n    suspense: true\n  })\n  return <div>{data || 'empty'}</div>\n}\n\nexport default function Page() {\n  const [trigger, toggle] = useReducer(x => !x, false)\n\n  return (\n    <div>\n      <button onClick={toggle}>toggle</button>\n      <Suspense fallback={<div>fallback</div>}>\n        <Section trigger={trigger} />\n      </Suspense>\n    </div>\n  )\n}\n"
  },
  {
    "path": "e2e/site/component/manual-retry-mutate.tsx",
    "content": "import { Suspense } from 'react'\nimport { ErrorBoundary } from 'react-error-boundary'\nimport useSWR from 'swr'\nimport { mutate } from 'swr'\n\nlet count = 0\nexport const fetcher = () => {\n  count++\n  if (count === 1) return Promise.reject('wrong')\n  return fetch('/api/retry')\n    .then(r => r.json())\n    .then(r => r.name)\n}\n\nconst key = 'manual-retry-mutate'\n\nexport const useRemoteData = () =>\n  useSWR(key, fetcher, {\n    suspense: true\n  })\nconst Demo = () => {\n  const { data } = useRemoteData()\n  return <div>data: {data}</div>\n}\n\nfunction Fallback({ resetErrorBoundary }: any) {\n  return (\n    <div role=\"alert\">\n      <p>Something went wrong</p>\n      <button\n        onClick={async () => {\n          await mutate(key, fetcher)\n          resetErrorBoundary()\n        }}\n      >\n        retry\n      </button>\n    </div>\n  )\n}\n\nfunction RemoteData() {\n  return (\n    <div className=\"App\">\n      <ErrorBoundary FallbackComponent={Fallback}>\n        <Suspense fallback={<div>loading</div>}>\n          <Demo />\n        </Suspense>\n      </ErrorBoundary>\n    </div>\n  )\n}\n\nexport default RemoteData\n"
  },
  {
    "path": "e2e/site/component/manual-retry.tsx",
    "content": "import { Suspense } from 'react'\nimport { ErrorBoundary } from 'react-error-boundary'\nimport { useRemoteData, preloadRemote } from './use-remote-data'\n\nconst Demo = () => {\n  const { data } = useRemoteData()\n  return <div>data: {data}</div>\n}\n\nfunction Fallback({ resetErrorBoundary }: any) {\n  return (\n    <div role=\"alert\">\n      <p>Something went wrong</p>\n      <button\n        onClick={() => {\n          resetErrorBoundary()\n        }}\n      >\n        retry\n      </button>\n    </div>\n  )\n}\n\nfunction RemoteData() {\n  return (\n    <div className=\"App\">\n      <ErrorBoundary\n        FallbackComponent={Fallback}\n        onReset={() => {\n          preloadRemote()\n        }}\n      >\n        <Suspense fallback={<div>loading</div>}>\n          <Demo />\n        </Suspense>\n      </ErrorBoundary>\n    </div>\n  )\n}\n\nexport default RemoteData\n"
  },
  {
    "path": "e2e/site/component/only-render-in-client.tsx",
    "content": "'use client'\nimport React from 'react'\n\n// This component ensures its children are only rendered on the client side.\nexport function OnlyRenderInClient({\n  children\n}: {\n  children?: React.ReactNode\n}) {\n  const [isClient, setIsClient] = React.useState(false)\n  React.useEffect(() => {\n    setIsClient(true)\n  }, [])\n  if (!isClient) {\n    return null\n  }\n  return <>{children}</>\n}\n"
  },
  {
    "path": "e2e/site/component/use-remote-data.ts",
    "content": "import useSWR from 'swr'\nimport { preload } from 'swr'\n\nlet count = 0\nexport const fetcher = () => {\n  count++\n  if (count === 1) return Promise.reject('wrong')\n  return fetch('/api/retry')\n    .then(r => r.json())\n    .then(r => r.name)\n}\n\nconst key = 'manual-retry-18-2'\n\nexport const useRemoteData = () =>\n  useSWR(key, fetcher, {\n    suspense: true\n  })\n\nexport const preloadRemote = () => preload(key, fetcher)\n"
  },
  {
    "path": "e2e/site/lib/sleep.ts",
    "content": "export function sleep(ms: number) {\n  return new Promise<void>(resolve => setTimeout(resolve, ms))\n}\n"
  },
  {
    "path": "e2e/site/lib/use-debug-history.ts",
    "content": "'use client'\nimport { useEffect, useRef } from 'react'\n\nexport const useDebugHistory = <T>(data: T, prefix = '') => {\n  const dataRef = useRef<T[]>([])\n  const debugRef = useRef<HTMLDivElement>(null)\n  useEffect(() => {\n    dataRef.current.push(data)\n    if (debugRef.current) {\n      debugRef.current.innerText = `${prefix}${JSON.stringify(dataRef.current)}`\n    }\n  }, [data, prefix])\n  return debugRef\n}\n"
  },
  {
    "path": "e2e/site/next-env.d.ts",
    "content": "/// <reference types=\"next\" />\n/// <reference types=\"next/image-types/global\" />\n/// <reference types=\"next/navigation-types/compat/navigation\" />\nimport './.next/types/routes.d.ts'\n\n// NOTE: This file should not be edited\n// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.\n"
  },
  {
    "path": "e2e/site/next.config.js",
    "content": "/** @type {import('next').NextConfig} */\nconst nextConfig = {}\n\nmodule.exports = nextConfig\n"
  },
  {
    "path": "e2e/site/package.json",
    "content": "{\n  \"name\": \"site\",\n  \"version\": \"0.1.0\",\n  \"private\": true,\n  \"scripts\": {\n    \"dev\": \"next dev\",\n    \"build\": \"next build\",\n    \"start\": \"next start\",\n    \"lint\": \"next lint\"\n  },\n  \"dependencies\": {\n    \"@types/node\": \"^22.19.1\",\n    \"@types/react\": \"^19.2.7\",\n    \"@types/react-dom\": \"^19.2.7\",\n    \"next\": \"^16.0.10\",\n    \"react\": \"^19.1.4\",\n    \"react-dom\": \"^19.1.4\",\n    \"typescript\": \"5.9.3\",\n    \"swr\": \"link:../../\"\n  }\n}\n"
  },
  {
    "path": "e2e/site/pages/api/data.ts",
    "content": "// Next.js API route support: https://nextjs.org/docs/api-routes/introduction\nimport type { NextApiRequest, NextApiResponse } from 'next'\n\ntype Data = {\n  name: string\n}\n\nexport default function handler(\n  req: NextApiRequest,\n  res: NextApiResponse<Data>\n) {\n  res.status(200).json({ name: 'SSR Works' })\n}\n"
  },
  {
    "path": "e2e/site/pages/api/retry.ts",
    "content": "import type { NextApiRequest, NextApiResponse } from 'next'\n\ntype Data = {\n  name: string\n}\n\nexport default function handler(\n  req: NextApiRequest,\n  res: NextApiResponse<Data>\n) {\n  res.status(200).json({ name: 'SWR suspense retry works' })\n}\n"
  },
  {
    "path": "e2e/site/pages/suspense-retry-19.tsx",
    "content": "import { Suspense } from 'react'\nimport dynamic from 'next/dynamic'\n\nconst RemoteData = dynamic(() => import('../component/manual-retry'), {\n  ssr: false\n})\n\nexport default function HomePage() {\n  return (\n    <Suspense fallback={<div>loading component</div>}>\n      <RemoteData></RemoteData>\n    </Suspense>\n  )\n}\n"
  },
  {
    "path": "e2e/site/pages/suspense-retry-mutate.tsx",
    "content": "import { Suspense } from 'react'\nimport dynamic from 'next/dynamic'\n\nconst RemoteData = dynamic(() => import('../component/manual-retry-mutate'), {\n  ssr: false\n})\n\nexport default function HomePage() {\n  return (\n    <Suspense fallback={<div>loading component</div>}>\n      <RemoteData></RemoteData>\n    </Suspense>\n  )\n}\n"
  },
  {
    "path": "e2e/site/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"es5\",\n    \"lib\": [\n      \"dom\",\n      \"dom.iterable\",\n      \"esnext\"\n    ],\n    \"allowJs\": true,\n    \"skipLibCheck\": true,\n    \"strict\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"noEmit\": true,\n    \"esModuleInterop\": true,\n    \"module\": \"esnext\",\n    \"moduleResolution\": \"node\",\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"jsx\": \"react-jsx\",\n    \"incremental\": true,\n    \"plugins\": [\n      {\n        \"name\": \"next\"\n      }\n    ],\n    \"baseUrl\": \".\",\n    \"paths\": {\n      \"~/*\": [\n        \"./*\"\n      ]\n    }\n  },\n  \"include\": [\n    \"next-env.d.ts\",\n    \"**/*.ts\",\n    \"**/*.tsx\",\n    \".next/types/**/*.ts\",\n    \".next/dev/types/**/*.ts\"\n  ],\n  \"exclude\": [\n    \"node_modules\"\n  ]\n}\n"
  },
  {
    "path": "e2e/test/concurrent-transition.test.ts",
    "content": "import { test, expect } from '@playwright/test'\n\ntest.describe('concurrent rendering transitions', () => {\n  test('should pause when changing the key inside a transition', async ({\n    page\n  }) => {\n    // Navigate to the test page\n    await page.goto('./concurrent-transition', { waitUntil: 'networkidle' })\n\n    // Wait for page to be fully loaded and interactive\n    await expect(page.getByTestId('pending-state')).toContainText('isPending:0')\n\n    // Wait for initial data to load\n    await expect(page.getByTestId('data-content')).toContainText(\n      'data:initial-key'\n    )\n\n    // Ensure the component is in a stable state before triggering transition\n    await page.waitForTimeout(100)\n\n    // Click to trigger transition\n    await page.getByTestId('transition-trigger').click()\n\n    // Verify transition starts - isPending becomes true\n    await expect(page.getByTestId('pending-state')).toContainText('isPending:1')\n\n    // During transition: data should still show old value (this is the key behavior)\n    // In React 19, this behavior should be more consistent\n    await expect(page.getByTestId('data-content')).toContainText(\n      'data:initial-key'\n    )\n\n    // Wait for transition to complete\n    await expect(page.getByTestId('pending-state')).toContainText('isPending:0')\n    await expect(page.getByTestId('data-content')).toContainText('data:new-key')\n  })\n})\n"
  },
  {
    "path": "e2e/test/initial-render.test.ts",
    "content": "import { test, expect } from '@playwright/test'\n\ntest.describe('rendering', () => {\n  test('suspense with preload', async ({ page }) => {\n    await page.goto('./suspense-after-preload', { waitUntil: 'commit' })\n    await page.getByRole('button', { name: 'preload' }).click()\n    await expect(page.getByText('suspense-after-preload')).toBeVisible()\n  })\n\n  test('should be able to retry in suspense with react 19 (app router)', async ({\n    page\n  }) => {\n    await page.goto('./suspense-retry', { waitUntil: 'commit' })\n    await expect(page.getByText('Something went wrong')).toBeVisible()\n    await page.getByRole('button', { name: 'retry' }).click()\n    await expect(page.getByText('data: SWR suspense retry works')).toBeVisible()\n  })\n\n  test('should be able to retry in suspense with react 19 (pages router)', async ({\n    page\n  }) => {\n    await page.goto('./suspense-retry-19', { waitUntil: 'commit' })\n    await expect(page.getByText('Something went wrong')).toBeVisible()\n    await page.getByRole('button', { name: 'retry' }).click()\n    await expect(page.getByText('data: SWR suspense retry works')).toBeVisible()\n  })\n\n  test('should be able to retry in suspense with mutate', async ({ page }) => {\n    await page.goto('./suspense-retry-mutate', { waitUntil: 'commit' })\n    await expect(page.getByText('Something went wrong')).toBeVisible()\n    await page.getByRole('button', { name: 'retry' }).click()\n    await expect(page.getByText('data: SWR suspense retry works')).toBeVisible()\n  })\n\n  test('should be able to use `unstable_serialize` in server component', async ({\n    page\n  }) => {\n    await page.goto('./react-server-entry', { waitUntil: 'commit' })\n    await expect(page.getByText('unstable_serialize: useSWR')).toBeVisible()\n    await expect(\n      page.getByText('infinite_unstable_serialize: $inf$useSWRInfinite')\n    ).toBeVisible()\n  })\n\n  test('swr should not cause extra rerenders', async ({ page }) => {\n    const renderLogs: string[] = []\n    await page.exposeFunction('consoleCount', (msg: string) => {\n      renderLogs.push(msg)\n    })\n    await page.addInitScript(() => {\n      const originalConsoleCount = console.count\n      console.count = (label?: string) => {\n        // @ts-ignore\n        window.consoleCount(label)\n        originalConsoleCount.call(console, label)\n      }\n    })\n    await page.goto('./render-count', { waitUntil: 'commit' })\n    // wait a bit to ensure no extra renders happen\n    await page.waitForTimeout(500)\n    const renderCount = renderLogs.filter(log => log === 'render').length\n    expect(renderCount).toBe(1)\n  })\n})\n"
  },
  {
    "path": "e2e/test/issue-2702-too-many-hooks.ts",
    "content": "import { test, expect } from '@playwright/test'\n\ntest.describe('issue 2702', () => {\n  test('should not crash with too many hooks', async ({ page }) => {\n    // Navigate to the test page\n    await page.goto('./issue-2702', { waitUntil: 'networkidle' })\n\n    // Wait for the page to be fully loaded and interactive\n    await expect(page.getByText('fetching')).toBeVisible()\n\n    // Verify that the component renders correctly\n    await expect(page.getByText('a,b')).toBeVisible()\n  })\n})\n"
  },
  {
    "path": "e2e/test/mutate-server-action.test.ts",
    "content": "import { test, expect } from '@playwright/test'\n\ntest('mutate-server-action', async ({ page }) => {\n  await page.goto('./mutate-server-action')\n  await page.getByRole('button', { name: 'mutate' }).click()\n  await expect(page.getByText('isMutating: true')).toBeVisible()\n  await expect(page.getByText('data: ')).toBeVisible()\n  await page.waitForTimeout(500)\n  await expect(page.getByText('isMutating: false')).toBeVisible()\n  await expect(page.getByText('data: 10086')).toBeVisible()\n})\n"
  },
  {
    "path": "e2e/test/perf.test.ts",
    "content": "import { test, expect } from '@playwright/test'\n\ntest.describe('performance', () => {\n  test('should render expensive component within 1 second after checkbox click', async ({\n    page\n  }) => {\n    // Navigate to the perf page\n    await page.goto('./perf', { waitUntil: 'load' })\n\n    // Inject performance measurement into the page\n    await page.evaluate(() => {\n      const checkboxInput = document.querySelector(\n        'input[type=\"checkbox\"]'\n      ) as HTMLInputElement\n      let expensiveComponentContainer: HTMLElement | null = null\n      const targetChildCount = 10_000\n      let startTime = 0\n\n      // Track when React starts and completes rendering\n      const observer = new MutationObserver(mutations => {\n        for (const mutation of mutations) {\n          const addedNodes = Array.from(mutation.addedNodes)\n          for (const node of addedNodes) {\n            if (node instanceof HTMLElement) {\n              const h2 = node.querySelector?.('h2')\n              if (h2?.textContent === 'Expensive Component') {\n                expensiveComponentContainer = node\n                console.log('Found Expensive Component container')\n              }\n            }\n          }\n\n          if (expensiveComponentContainer) {\n            const renderedComponents =\n              expensiveComponentContainer.querySelectorAll('div > div').length\n\n            if (renderedComponents % 1000 === 0 && renderedComponents > 0) {\n              console.log(`Rendered ${renderedComponents} components...`)\n            }\n\n            if (renderedComponents >= targetChildCount) {\n              console.log(`All ${renderedComponents} components rendered!`)\n\n              // Use requestAnimationFrame to ensure the browser has painted\n              requestAnimationFrame(() => {\n                requestAnimationFrame(() => {\n                  // Double RAF to ensure we're after the paint\n                  window.performance.mark('expensive-component-painted')\n                  const paintTime = performance.now() - startTime\n                  console.log(\n                    `Total time including paint: ${paintTime.toFixed(2)}ms`\n                  )\n                })\n              })\n\n              observer.disconnect()\n            }\n          }\n        }\n      })\n\n      observer.observe(document.body, { childList: true, subtree: true })\n\n      // Capture more precise timing\n      checkboxInput.addEventListener(\n        'click',\n        () => {\n          startTime = performance.now()\n          window.performance.mark('state-change-start')\n          console.log('Checkbox clicked, state change started')\n        },\n        { once: true }\n      )\n    })\n\n    // Find and click the checkbox\n    const checkbox = page.locator('input[type=\"checkbox\"]')\n    await checkbox.click()\n\n    // Wait for the expensive component to be fully rendered\n    const expensiveComponentHeading = page.locator(\n      'h2:has-text(\"Expensive Component\")'\n    )\n    await expect(expensiveComponentHeading).toBeVisible({ timeout: 60_000 })\n\n    // Wait for all components and paint to complete\n    await page.waitForFunction(\n      () => {\n        return (\n          window.performance.getEntriesByName('expensive-component-painted')\n            .length > 0\n        )\n      },\n      { timeout: 5000 }\n    )\n\n    // Get the render time from state change to paint\n    const renderTime = await page.evaluate(() => {\n      const startMark =\n        window.performance.getEntriesByName('state-change-start')[0]\n      const paintMark = window.performance.getEntriesByName(\n        'expensive-component-painted'\n      )[0]\n\n      if (!startMark || !paintMark) {\n        throw new Error('Performance marks not found')\n      }\n\n      return paintMark.startTime - startMark.startTime\n    })\n\n    // Assert that the rendering took less than 1 second (1000ms)\n    expect(renderTime).toBeLessThan(1000)\n  })\n})\n"
  },
  {
    "path": "e2e/test/preload-scenarios.test.ts",
    "content": "import { expect, test } from '@playwright/test'\n\ntest.describe('preload scenarios', () => {\n  test('preloads fetcher before mount', async ({ page }) => {\n    await page.goto('./render-preload-basic', { waitUntil: 'commit' })\n\n    await expect(page.getByTestId('data')).toHaveText('data:foo')\n    await expect(page.getByTestId('fetch-count')).toHaveText('fetches: 1')\n  })\n\n  test('avoids suspense waterfall when preloading multiple resources', async ({\n    page\n  }) => {\n    await page.goto('./render-preload-avoid-waterfall', {\n      waitUntil: 'commit'\n    })\n    await expect(page.getByTestId('fallback')).toBeVisible()\n    await page.waitForTimeout(200)\n    await expect(page.getByTestId('fallback')).not.toBeVisible()\n    await expect(page.getByTestId('data')).toHaveText('data:foo:bar')\n  })\n})\n"
  },
  {
    "path": "e2e/test/promise-scenarios.test.ts",
    "content": "import { expect, test } from '@playwright/test'\n\ntest.describe('promise scenarios', () => {\n  test('suspends while resolving fallback promise', async ({ page }) => {\n    await page.goto('./render-promise-suspense-resolve', {\n      waitUntil: 'commit'\n    })\n\n    await expect(page.getByTestId('fallback')).toBeVisible()\n\n    const history = page.getByTestId('history')\n\n    await expect(history).toContainText('\"initial data\"')\n\n    await expect(page.getByTestId('data')).toHaveText('data:new data')\n    await expect(history).toHaveText('history:[\"initial data\",\"new data\"]')\n\n    await expect(page.getByTestId('fallback')).toHaveCount(0)\n  })\n\n  test('surfaces error boundary when fallback promise rejects', async ({\n    page\n  }) => {\n    await page.goto('./render-promise-suspense-error', {\n      waitUntil: 'commit'\n    })\n\n    await expect(page.getByTestId('fallback')).toBeVisible()\n\n    await expect(page.getByTestId('error')).toHaveText('error')\n    await expect(page.getByTestId('fallback')).toHaveCount(0)\n    await expect(page.getByTestId('data')).toHaveCount(0)\n  })\n\n  test('resolves shared fallback promise once for multiple consumers', async ({\n    page\n  }) => {\n    await page.goto('./render-promise-suspense-shared', {\n      waitUntil: 'commit'\n    })\n\n    await expect(page.getByTestId('fallback')).toBeVisible()\n\n    await expect(page.getByTestId('data-first')).toHaveText('data:value')\n    await expect(page.getByTestId('data-second')).toHaveText('data:value')\n    await expect(page.getByTestId('fallback')).toHaveCount(0)\n  })\n})\n"
  },
  {
    "path": "e2e/test/server-prefetch-warning.test.ts",
    "content": "import { test, expect } from '@playwright/test'\n\nconst warningForKey = (key: string) =>\n  `Missing pre-initiated data for serialized key \"${key}\" during server-side rendering. Data fetching should be initiated on the server and provided to SWR via fallback data. You can set \"strictServerPrefetchWarning: false\" to disable this warning.`\n\ntest.describe('strictServerPrefetchWarning', () => {\n  test('warns on hydration when data is missing', async ({ page }) => {\n    const warnings: string[] = []\n\n    page.on('console', msg => {\n      if (msg.type() !== 'warning') return\n      const text = msg.text()\n      if (text.includes('Missing pre-initiated data')) {\n        warnings.push(text)\n      }\n    })\n\n    await page.goto('./server-prefetch-warning', { waitUntil: 'commit' })\n    await expect(page.getByTestId('hydration-state')).toHaveText('hydrated')\n\n    await expect.poll(() => warnings.length).toBe(2)\n    expect(warnings).toEqual(\n      expect.arrayContaining([warningForKey('ssr:1'), warningForKey('ssr:2')])\n    )\n    expect(warnings).toHaveLength(2)\n  })\n})\n"
  },
  {
    "path": "e2e/test/stream-ssr.test.ts",
    "content": "import { test, expect } from '@playwright/test'\n\ntest.describe('Stream SSR', () => {\n  test('Basic SSR', async ({ page }) => {\n    const log: any[] = []\n    await page.exposeFunction('consoleError', (msg: any) => log.push(msg))\n    await page.addInitScript(`\n      const onError = window.onerror\n      window.onerror = (...args) => {\n        consoleError(...args)\n        onError(...args)\n      }\n    `)\n    await page.goto('./basic-ssr', { waitUntil: 'commit' })\n    await expect(page.getByText('result:undefined')).toBeVisible()\n    await expect(page.getByText('result:SSR Works')).toBeVisible()\n    await expect(page.getByText('history:[null,\"SSR Works\"]')).toBeVisible()\n    expect(log).toHaveLength(0)\n  })\n\n  test('Partially Hydrate', async ({ page }) => {\n    const log: any[] = []\n    await page.exposeFunction('consoleError', (msg: any) => log.push(msg))\n    await page.addInitScript(`\n      const onError = window.onerror\n      window.onerror = (...args) => {\n        consoleError(...args)\n        onError(...args)\n      }\n    `)\n    await page.goto('./partially-hydrate', { waitUntil: 'commit' })\n    await expect(page.getByText('first data:undefined')).toBeVisible()\n    await expect(\n      page.getByText('second data (delayed hydration):undefined')\n    ).toBeVisible()\n    await expect(page.getByText('first data:SSR Works')).toBeVisible()\n    await expect(\n      page.getByText('second data (delayed hydration):SSR Works')\n    ).toBeVisible()\n    await expect(\n      page.getByText('first history:[null,\"SSR Works\"]')\n    ).toBeVisible()\n    await expect(\n      page.getByText('second history:[null,\"SSR Works\"]')\n    ).toBeVisible()\n    expect(log).toHaveLength(0)\n  })\n})\n"
  },
  {
    "path": "e2e/test/suspense-fallback.test.ts",
    "content": "import { test, expect } from '@playwright/test'\n\ntest.describe('suspense fallback', () => {\n  test('should wait for promise fallback value to be resolved', async ({\n    page\n  }) => {\n    await page.goto('./suspense-fallback/promise', { waitUntil: 'commit' })\n    await expect(page.getByText('async promise')).toBeVisible()\n  })\n})\n"
  },
  {
    "path": "e2e/test/suspense-scenarios.test.ts",
    "content": "import { expect, test } from '@playwright/test'\n\n// Group all suspense-related scenarios so they run together.\ntest.describe('suspense scenarios', () => {\n  test('waits for promise fallback value to resolve', async ({ page }) => {\n    await page.goto('./suspense-fallback/promise', { waitUntil: 'commit' })\n    await expect(page.getByText('async promise')).toBeVisible()\n  })\n\n  test('updates data when suspense key changes', async ({ page }) => {\n    await page.goto('./suspense-infinite-get-key', { waitUntil: 'commit' })\n\n    await expect(page.getByTestId('data')).toHaveText('data: A1,A2,A3')\n\n    await page.getByRole('button', { name: 'mutate' }).click()\n\n    await expect(page.getByTestId('data')).toHaveText('data: B1,B2,B3')\n  })\n\n  test('shows fallback before resolved data renders', async ({ page }) => {\n    await page.goto('./render-suspense-fallback', { waitUntil: 'commit' })\n\n    await expect(page.getByTestId('fallback')).toBeVisible()\n    await expect(page.getByTestId('data')).toHaveText('SWR')\n  })\n\n  test('keeps fallback visible until multiple resources resolve', async ({\n    page\n  }) => {\n    await page.goto('./render-suspense-multiple-fallbacks', {\n      waitUntil: 'commit'\n    })\n\n    const fallback = page.getByTestId('fallback')\n    await expect(fallback).toBeVisible()\n\n    await page.waitForTimeout(80)\n    await expect(fallback).toBeVisible()\n\n    await page.waitForTimeout(120)\n    await expect(page.getByTestId('data')).toHaveText('3')\n  })\n\n  test('renders synchronously resolved data without fallback', async ({\n    page\n  }) => {\n    await page.goto('./render-suspense-non-promise', { waitUntil: 'commit' })\n\n    await expect(page.getByTestId('fallback')).toHaveCount(0)\n    await expect(page.getByTestId('data')).toHaveText('hello')\n  })\n\n  test('surfaces error boundary after suspense fallback', async ({ page }) => {\n    await page.goto('./render-suspense-error', { waitUntil: 'commit' })\n\n    await expect(page.getByTestId('fallback')).toBeVisible()\n    await expect(page.getByTestId('error')).toBeVisible()\n  })\n\n  test.skip('retains cached data while surfacing errors', async ({ page }) => {\n    await page.goto('./render-suspense-cached-error', { waitUntil: 'commit' })\n\n    await expect(page.getByTestId('fallback')).toHaveCount(0)\n    await expect(page.getByTestId('result')).toHaveText('data: hello, error: ')\n    await expect(page.getByTestId('result')).toHaveText(\n      'data: hello, error: error'\n    )\n  })\n\n  test('skips revalidation when data is cached and revalidateIfStale is false', async ({\n    page\n  }) => {\n    await page.goto('./render-suspense-no-revalidate', { waitUntil: 'commit' })\n\n    const data = page.getByTestId('data')\n    await expect(data).toHaveText('data: cached')\n\n    await page.waitForTimeout(200)\n    await expect(data).toHaveText('data: cached')\n  })\n\n  test('pauses while key changes and resumes with new data', async ({\n    page\n  }) => {\n    await page.goto('./render-suspense-key-change', { waitUntil: 'commit' })\n\n    const fallback = page.getByTestId('fallback')\n    const data = page.getByTestId('data')\n\n    await expect(fallback).toBeVisible()\n    await expect(data).toHaveText('data: render-suspense-key-change-initial')\n\n    await page.getByTestId('toggle').click()\n\n    await expect(fallback).toBeVisible()\n    await expect(data).toHaveText('data: render-suspense-key-change-updated')\n  })\n\n  test('renders correctly when key changes with identical data', async ({\n    page\n  }) => {\n    await page.goto('./render-suspense-same-data', { waitUntil: 'commit' })\n\n    const fallback = page.getByTestId('fallback')\n    const data = page.getByTestId('data')\n\n    await expect(fallback).toBeVisible()\n    await expect(data).toHaveText('data: 123,1')\n\n    await page.getByTestId('increment').click()\n\n    await expect(fallback).toBeVisible()\n    await expect(data).toHaveText('data: 123,2')\n  })\n\n  test('renders initial data when provided', async ({ page }) => {\n    await page.goto('./render-suspense-initial-data', { waitUntil: 'commit' })\n\n    await expect(page.getByTestId('fallback')).toHaveCount(0)\n    await expect(page.getByTestId('data')).toHaveText('hello, Initial')\n    await page.waitForTimeout(200)\n    await expect(page.getByTestId('data')).toHaveText('hello, SWR')\n  })\n\n  test('avoids unnecessary re-renders', async ({ page }) => {\n    await page.goto('./render-suspense-avoid-rerender', { waitUntil: 'commit' })\n\n    await expect(page.getByTestId('fallback')).toBeVisible()\n    await expect(page.getByTestId('start-count')).toHaveText('start renders: 1')\n\n    await page.waitForTimeout(200)\n\n    await expect(page.getByTestId('fallback')).toHaveCount(0)\n    await expect(page.getByTestId('data-count')).toHaveText('data renders: 1')\n    await expect(page.getByTestId('start-count')).toHaveText('start renders: 1')\n  })\n\n  test('renders fallback only once when keepPreviousData is true', async ({\n    page\n  }) => {\n    await page.goto('./render-suspense-keep-previous', { waitUntil: 'commit' })\n\n    const fallback = page.getByTestId('fallback')\n    const data = page.getByTestId('data')\n\n    await expect(fallback).toBeVisible()\n\n    await expect(data).toHaveText('data:origin')\n    await expect(fallback).toHaveCount(0)\n\n    await page.getByTestId('set-next').click()\n\n    await expect(fallback).toHaveCount(0)\n    await expect(data).toHaveText('data:origin')\n    await expect(data).toHaveText('data:next')\n    await expect(fallback).toHaveCount(0)\n  })\n\n  test('updates fetcher reference with suspense', async ({ page }) => {\n    await page.goto('./render-suspense-fetcher', { waitUntil: 'commit' })\n    const fallback = page.getByTestId('fallback')\n\n    await expect(fallback).toBeVisible()\n    await page.waitForTimeout(100)\n    await expect(page.getByTestId('data')).toHaveText('data:foo')\n\n    await page.getByTestId('set-bar').click()\n    await expect(fallback).toBeVisible()\n    await expect(page.getByTestId('data')).toHaveText('data:bar')\n  })\n\n  test('preloads infinite data for suspense', async ({ page }) => {\n    await page.goto('./render-suspense-infinite-preload', {\n      waitUntil: 'commit'\n    })\n\n    await expect(page.getByTestId('data')).toHaveText(\n      'render-suspense-infinite-preload-0-value'\n    )\n    await expect(page.getByTestId('fetch-count')).toHaveText('fetches: 1')\n    await expect(page.getByTestId('fallback-count')).toHaveText(\n      'fallback renders: 1'\n    )\n    await expect(page.getByTestId('fallback')).toHaveCount(0)\n  })\n\n  test('renders correctly when key changes from null to valid', async ({\n    page\n  }) => {\n    await page.goto('./render-suspense-null-key', { waitUntil: 'commit' })\n\n    const data = page.getByTestId('data')\n\n    await expect(data).toHaveText('render-suspense-null-key-123')\n\n    await page.getByTestId('set-empty').click()\n    await expect(data).toHaveText('render-suspense-null-key-nodata')\n\n    await page.getByTestId('set-456').click()\n    await expect(data).toHaveText('render-suspense-null-key-456')\n  })\n\n  test('handles undefined key toggling', async ({ page }) => {\n    await page.goto('./suspense-undefined-key', { waitUntil: 'commit' })\n\n    await expect(page.getByText('empty')).toBeVisible()\n\n    await page.getByRole('button', { name: 'toggle' }).click()\n\n    await expect(page.getByText('fallback')).toBeVisible()\n    await expect(page.getByText('SWR')).toBeVisible()\n  })\n})\n"
  },
  {
    "path": "e2e/test/suspense-undefined-key.test.ts",
    "content": "import { test, expect } from '@playwright/test'\n\ntest.describe('suspense with undefined key', () => {\n  test('should render correctly when key is undefined', async ({ page }) => {\n    await page.goto('./suspense-undefined-key', { waitUntil: 'commit' })\n\n    // Should show content for undefined key (not suspense)\n    await expect(page.getByText('empty')).toBeVisible()\n\n    // Click toggle to enable key\n    await page.getByRole('button', { name: 'toggle' }).click()\n\n    // Should show loading fallback when key becomes defined\n    await expect(page.getByText('fallback')).toBeVisible()\n\n    // Should eventually show the fetched data\n    await expect(page.getByText('SWR')).toBeVisible()\n  })\n})\n"
  },
  {
    "path": "e2e/test/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig.json\",\n  \"include\": [\n    \".\",\n  ],\n}"
  },
  {
    "path": "eslint.config.mjs",
    "content": "import { defineConfig, globalIgnores } from 'eslint/config'\nimport typescriptEslint from '@typescript-eslint/eslint-plugin'\nimport reactHooks from 'eslint-plugin-react-hooks'\nimport globals from 'globals'\nimport tsParser from '@typescript-eslint/parser'\nimport path from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport js from '@eslint/js'\nimport { FlatCompat } from '@eslint/eslintrc'\n\nconst __filename = fileURLToPath(import.meta.url)\nconst __dirname = path.dirname(__filename)\nconst compat = new FlatCompat({\n  baseDirectory: __dirname,\n  recommendedConfig: js.configs.recommended,\n  allConfig: js.configs.all\n})\n\nexport default defineConfig([\n  globalIgnores([\n    '**/dist/',\n    '**/node_modules',\n    '**/scripts',\n    '**/examples',\n    '**/.next',\n    '**/next.config.js',\n    'eslint.config.mjs',\n    'playwright.config.js',\n    'jest.config.js',\n    'jest.config.build.js',\n    'test-result/**',\n    'playwright-report/**'\n  ]),\n  {\n    extends: compat.extends(\n      'eslint:recommended',\n      'plugin:react/recommended',\n      'plugin:@typescript-eslint/recommended',\n      'prettier',\n      'plugin:jest-dom/recommended',\n      'plugin:testing-library/react',\n      'plugin:react/jsx-runtime'\n    ),\n    plugins: {\n      '@typescript-eslint': typescriptEslint,\n      'react-hooks': reactHooks\n    },\n    languageOptions: {\n      globals: {\n        ...globals.browser,\n        ...globals.node,\n        ...globals.jest\n      },\n      parser: tsParser,\n      ecmaVersion: 8,\n      sourceType: 'module',\n      parserOptions: {\n        ecmaFeatures: {\n          impliedStrict: true,\n          experimentalObjectRestSpread: true\n        },\n        allowImportExportEverywhere: true,\n        project: ['**/tsconfig.json']\n      }\n    },\n    settings: {\n      react: {\n        version: 'detect'\n      }\n    },\n    rules: {\n      'func-names': [2, 'as-needed'],\n      'no-shadow': 0,\n      '@typescript-eslint/no-shadow': 2,\n      '@typescript-eslint/explicit-function-return-type': 0,\n      '@typescript-eslint/no-unused-vars': [\n        0,\n        {\n          argsIgnorePattern: '^_'\n        }\n      ],\n      '@typescript-eslint/no-use-before-define': 0,\n      '@typescript-eslint/ban-ts-ignore': 0,\n      '@typescript-eslint/no-empty-function': 0,\n      '@typescript-eslint/ban-ts-comment': 0,\n      '@typescript-eslint/no-var-requires': 0,\n      '@typescript-eslint/no-explicit-any': 0,\n      '@typescript-eslint/explicit-module-boundary-types': 0,\n      '@typescript-eslint/consistent-type-imports': [\n        2,\n        {\n          prefer: 'type-imports'\n        }\n      ],\n      '@typescript-eslint/ban-types': 0,\n      'react-hooks/rules-of-hooks': 2,\n      'react-hooks/exhaustive-deps': 1,\n      'react/prop-types': 0,\n      'testing-library/no-unnecessary-act': 0\n    }\n  },\n  {\n    files: ['e2e/**/*.ts'],\n    rules: {\n      'testing-library/prefer-screen-queries': 'off'\n    }\n  }\n])\n"
  },
  {
    "path": "examples/.eslintrc",
    "content": "// next is loading eslintrc from the root directory, adding this to avoid eslint rules being overridden\n{}\n"
  },
  {
    "path": "examples/api-hooks/README.md",
    "content": "# API Hooks\n\n## One-Click Deploy\n\nDeploy your own SWR project with Vercel.\n\n[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?s=https://github.com/vercel/swr/tree/main/examples/api-hooks)\n\n## How to Use\n\nDownload the example:\n\n```bash\ncurl https://codeload.github.com/vercel/swr/tar.gz/main | tar -xz --strip=2 swr-main/examples/api-hooks\ncd api-hooks\n```\n\nInstall it and run:\n\n```bash\nyarn\nyarn dev\n# or\nnpm install\nnpm run dev\n```\n\n## The Idea behind the Example\n\nShow how you could create custom hooks, using SWR internally, for your different data requirements and use them in your application.\n"
  },
  {
    "path": "examples/api-hooks/hooks/use-projects.js",
    "content": "import useSWR from 'swr'\n\nimport fetch from '../libs/fetch'\n\nexport default function useProjects() {\n  return useSWR('/api/data', fetch)\n}\n\n"
  },
  {
    "path": "examples/api-hooks/hooks/use-repository.js",
    "content": "import useSWR from 'swr'\n\nimport fetch from '../libs/fetch'\n\nexport default function useRepository(id) {\n  return useSWR('/api/data?id=' + id, fetch)\n}\n"
  },
  {
    "path": "examples/api-hooks/libs/fetch.js",
    "content": "export default async function fetcher(...args) {\n  const res = await fetch(...args)\n  return res.json()\n}\n"
  },
  {
    "path": "examples/api-hooks/package.json",
    "content": "{\n  \"name\": \"api-hooks\",\n  \"version\": \"1.0.0\",\n  \"main\": \"index.js\",\n  \"license\": \"MIT\",\n  \"dependencies\": {\n    \"next\": \"latest\",\n    \"react\": \"latest\",\n    \"react-dom\": \"latest\",\n    \"swr\": \"latest\"\n  },\n  \"scripts\": {\n    \"dev\": \"next\",\n    \"start\": \"next start\",\n    \"build\": \"next build\"\n  }\n}\n"
  },
  {
    "path": "examples/api-hooks/pages/[user]/[repo].js",
    "content": "import Link from 'next/link'\nimport useRepository from '../../hooks/use-repository'\n\nexport default function Repo() {\n  const id = typeof window !== 'undefined' ? window.location.pathname.slice(1) : ''\n  const { data } = useRepository(id)\n\n  return (\n    <div style={{ textAlign: 'center' }}>\n      <h1>{id}</h1>\n      {\n        data ? <div>\n          <p>forks: {data.forks_count}</p>\n          <p>stars: {data.stargazers_count}</p>\n          <p>watchers: {data.watchers}</p>\n        </div> : 'loading...'\n      }\n      <br />\n      <br />\n      <Link href=\"/\">Back</Link>\n    </div>\n  )\n}\n"
  },
  {
    "path": "examples/api-hooks/pages/api/data.js",
    "content": "const projects = [\n  'facebook/flipper', 'vuejs/vuepress', 'rust-lang/rust', 'vercel/next.js'\n]\n\nexport default (req, res) => {\n  if (req.query.id) {\n    // a slow endpoint for getting repo data\n    fetch(`https://api.github.com/repos/${req.query.id}`)\n      .then(resp => resp.json())\n      .then(data => {\n        setTimeout(() => {\n          res.json(data)\n        }, 2000)\n      })\n\n    return\n  }\n  setTimeout(() => {\n    res.json(projects)\n  }, 2000)\n}\n"
  },
  {
    "path": "examples/api-hooks/pages/index.js",
    "content": "import Link from 'next/link'\nimport useProjects from '../hooks/use-projects'\n\nexport default function Index() {\n  const { data } = useProjects()\n\n  return (\n    <div style={{ textAlign: 'center' }}>\n      <h1>Trending Projects</h1>\n      <div>\n      {\n        data ? data.map(project =>\n          <p key={project}><Link href='/[user]/[repo]' as={`/${project}`}>{project}</Link></p>\n        ) : 'loading...'\n      }\n      </div>\n    </div>\n  )\n}\n"
  },
  {
    "path": "examples/autocomplete-suggestions/README.md",
    "content": "# Autocomplete Suggestions\n\n## One-Click Deploy\n\nDeploy your own SWR project with Vercel.\n\n[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?s=https://github.com/vercel/swr/tree/main/examples/autocomplete-suggestions)\n\n## How to Use\n\nDownload the example:\n\n```bash\ncurl https://codeload.github.com/vercel/swr/tar.gz/main | tar -xz --strip=2 swr-main/examples/autocomplete-suggestions\ncd autocomplete-suggestions\n```\n\nInstall it and run:\n\n```bash\nyarn\nyarn dev\n# or\nnpm install\nnpm run dev\n```\n\n## The Idea behind the Example\n\nShow how to use SWR to fetch the suggestion for an autocomplete.\n"
  },
  {
    "path": "examples/autocomplete-suggestions/libs/fetcher.js",
    "content": "export default async function fetcher(...args) {\n  const res = await fetch(...args)\n  return res.json()\n}\n"
  },
  {
    "path": "examples/autocomplete-suggestions/package.json",
    "content": "{\n  \"name\": \"autocomplete-suggestions\",\n  \"version\": \"1.0.0\",\n  \"main\": \"index.js\",\n  \"license\": \"MIT\",\n  \"dependencies\": {\n    \"@reach/combobox\": \"0.16.1\",\n    \"lodash.debounce\": \"4.0.8\",\n    \"next\": \"latest\",\n    \"react\": \"latest\",\n    \"react-dom\": \"latest\",\n    \"swr\": \"latest\"\n  },\n  \"scripts\": {\n    \"dev\": \"next\",\n    \"start\": \"next start\",\n    \"build\": \"next build\"\n  }\n}\n"
  },
  {
    "path": "examples/autocomplete-suggestions/pages/api/suggestions.js",
    "content": "const countries = [\n  \"United States\",\n  \"Canada\",\n  \"United Kingdom\",\n  \"Argentina\",\n  \"Australia\",\n  \"Austria\",\n  \"Belgium\",\n  \"Brazil\",\n  \"Chile\",\n  \"China\",\n  \"Colombia\",\n  \"Croatia\",\n  \"Denmark\",\n  \"Dominican Republic\",\n  \"Egypt\",\n  \"Finland\",\n  \"France\",\n  \"Germany\",\n  \"Greece\",\n  \"Hong Kong\",\n  \"India\",\n  \"Indonesia\",\n  \"Ireland\",\n  \"Israel\",\n  \"Italy\",\n  \"Japan\",\n  \"Jordan\",\n  \"Kuwait\",\n  \"Lebanon\",\n  \"Malaysia\",\n  \"Mexico\",\n  \"Netherlands\",\n  \"New Zealand\",\n  \"Nigeria\",\n  \"Norway\",\n  \"Pakistan\",\n  \"Panama\",\n  \"Peru\",\n  \"Philippines\",\n  \"Poland\",\n  \"Russia\",\n  \"Saudi Arabia\",\n  \"Serbia\",\n  \"Singapore\",\n  \"South Africa\",\n  \"South Korea\",\n  \"Spain\",\n  \"Sweden\",\n  \"Switzerland\",\n  \"Taiwan\",\n  \"Thailand\",\n  \"Turkey\",\n  \"United Arab Emirates\",\n  \"Venezuela\",\n  \"Portugal\",\n  \"Luxembourg\",\n  \"Bulgaria\",\n  \"Czech Republic\",\n  \"Slovenia\",\n  \"Iceland\",\n  \"Slovakia\",\n  \"Lithuania\",\n  \"Trinidad and Tobago\",\n  \"Bangladesh\",\n  \"Sri Lanka\",\n  \"Kenya\",\n  \"Hungary\",\n  \"Morocco\",\n  \"Cyprus\",\n  \"Jamaica\",\n  \"Ecuador\",\n  \"Romania\",\n  \"Bolivia\",\n  \"Guatemala\",\n  \"Costa Rica\",\n  \"Qatar\",\n  \"El Salvador\",\n  \"Honduras\",\n  \"Nicaragua\",\n  \"Paraguay\",\n  \"Uruguay\",\n  \"Puerto Rico\",\n  \"Bosnia and Herzegovina\",\n  \"Palestinian Authority\",\n  \"Tunisia\",\n  \"Bahrain\",\n  \"Vietnam\",\n  \"Ghana\",\n  \"Mauritius\",\n  \"Ukraine\",\n  \"Malta\",\n  \"Bahamas\",\n  \"Maldives\",\n  \"Oman\",\n  \"Macedonia\",\n  \"Latvia\",\n  \"Estonia\",\n  \"Iraq\",\n  \"Algeria\",\n  \"Albania\",\n  \"Nepal\",\n  \"Macau\",\n  \"Montenegro\",\n  \"Senegal\",\n  \"Georgia\",\n  \"Brunei\",\n  \"Uganda\",\n  \"Guadeloupe\",\n  \"Barbados\",\n  \"Azerbaijan\",\n  \"Tanzania\",\n  \"Libya\",\n  \"Martinique\",\n  \"Cameroon\",\n  \"Botswana\",\n  \"Ethiopia\",\n  \"Kazakhstan\",\n  \"Namibia\",\n  \"Netherlands Antilles\",\n  \"Madagascar\",\n  \"New Caledonia\",\n  \"Moldova\",\n  \"Fiji\",\n  \"Belarus\",\n  \"Jersey\",\n  \"Guam\",\n  \"Yemen\",\n  \"Zambia\",\n  \"Isle Of Man\",\n  \"Haiti\",\n  \"Cambodia\",\n  \"Aruba\",\n  \"French Polynesia\",\n  \"Afghanistan\",\n  \"Bermuda\",\n  \"Guyana\",\n  \"Armenia\",\n  \"Malawi\",\n  \"Antigua and Barbuda\",\n  \"Rwanda\",\n  \"Guernsey\",\n  \"Gambia\",\n  \"Faroe Islands\",\n  \"St. Lucia\",\n  \"Cayman Islands\",\n  \"Benin\",\n  \"Andorra\",\n  \"Grenada\",\n  \"US Virgin Islands\",\n  \"Belize\",\n  \"Saint Vincent and the Grenadines\",\n  \"Mongolia\",\n  \"Mozambique\",\n  \"Mali\",\n  \"Angola\",\n  \"French Guiana\",\n  \"Uzbekistan\",\n  \"Djibouti\",\n  \"Burkina Faso\",\n  \"Monaco\",\n  \"Togo\",\n  \"Greenland\",\n  \"Gabon\",\n  \"Gibraltar\",\n  \"Republic of the Congo\",\n  \"Kyrgyzstan\",\n  \"Papua New Guinea\",\n  \"Bhutan\",\n  \"Saint Kitts and Nevis\",\n  \"Swaziland\",\n  \"Lesotho\",\n  \"Laos\",\n  \"Liechtenstein\",\n  \"Northern Mariana Islands\",\n  \"Suriname\",\n  \"Seychelles\",\n  \"British Virgin Islands\",\n  \"Turks and Caicos Islands\",\n  \"Dominica\",\n  \"Mauritania\",\n  \"Aland Islands\",\n  \"San Marino\",\n  \"Sierra Leone\",\n  \"Niger\",\n  \"Democratic Republic of the Congo\",\n  \"Anguilla\",\n  \"Mayotte\",\n  \"Cape Verde\",\n  \"Guinea\",\n  \"Turkmenistan\",\n  \"Burundi\",\n  \"Tajikistan\",\n  \"Vanuatu\",\n  \"Solomon Islands\",\n  \"Eritrea\",\n  \"Samoa\",\n  \"American Samoa\",\n  \"Falkland Islands\",\n  \"Equatorial Guinea\",\n  \"Tonga\",\n  \"Comoros\",\n  \"Palau\",\n  \"Federated States of Micronesia\",\n  \"Central African Republic\",\n  \"Somalia\",\n  \"Marshall Islands\",\n  \"Vatican City\",\n  \"Chad\",\n  \"Kiribati\",\n  \"Sao Tome and Principe\",\n  \"Tuvalu\",\n  \"Nauru\",\n  \"Réunion\",\n  \"Cuba\",\n  \"Cote d'Ivoire\",\n  \"Timor Leste\",\n  \"North Korea\",\n  \"Iran\",\n  \"Liberia\",\n  \"Myanmar\",\n  \"Oman\",\n  \"Saint Lucia\",\n  \"Syria\",\n  \"Sudan\"\n]\n\nexport default function suggestions(req, res) {\n  const results = countries\n    .filter(country => country.toLowerCase().startsWith(req.query.value))\n\n  res.json(results);\n}\n"
  },
  {
    "path": "examples/autocomplete-suggestions/pages/index.js",
    "content": "import { useMemo, useState } from \"react\"\nimport fetcher from '../libs/fetcher'\nimport {\n  Combobox,\n  ComboboxInput,\n  ComboboxPopover,\n  ComboboxList,\n  ComboboxOption\n} from '@reach/combobox'\nimport debounce from 'lodash.debounce'\n\nimport useSWR from 'swr'\n\nexport default function Index() {\n  const [searchTerm, setSearchTerm] = useState(null)\n  const { data: countries = [], isValidating } = useSWR(\n    () => (searchTerm ? `/api/suggestions?value=${searchTerm}` : null),\n    fetcher\n  )\n\n  function handleChange(event) {\n    setSearchTerm(event.target.value)\n  }\n\n  const debouncedHandleChange = useMemo(\n    () => debounce(handleChange, 500)\n  , []);\n\n  return (\n    <div style={{ textAlign: 'center' }}>\n      <h1>Country Search</h1>\n      <Combobox>\n        <ComboboxInput\n          className=\"country-search-input\"\n          onChange={debouncedHandleChange}\n          aria-label=\"Countries\"\n        />\n        {countries && countries.length > 0 && (\n          <ComboboxPopover className=\"shadow-popup\">\n            <ComboboxList>\n              {countries.map(country => (\n                <ComboboxOption key={country} value={country} />\n              ))}\n            </ComboboxList>\n          </ComboboxPopover>\n        )}\n      </Combobox>\n    </div>\n  )\n}\n"
  },
  {
    "path": "examples/axios/README.md",
    "content": "# Axios\n\n## One-Click Deploy\n\nDeploy your own SWR project with Vercel.\n\n[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?s=https://github.com/vercel/swr/tree/main/examples/axios)\n\n## How to Use\n\nDownload the example:\n\n```bash\ncurl https://codeload.github.com/vercel/swr/tar.gz/main | tar -xz --strip=2 swr-main/examples/axios\ncd axios\n```\n\nInstall it and run:\n\n```bash\nyarn\nyarn dev\n# or\nnpm install\nnpm run dev\n```\n\n## The Idea behind the Example\n\nShow a basic usage of SWR fetching using axios and a request object.\n"
  },
  {
    "path": "examples/axios/libs/useRequest.js",
    "content": "import useSWR from 'swr'\nimport axios from 'axios'\n\nexport default function useRequest(request, { fallbackData, ...config } = {}) {\n  return useSWR(\n    request,\n    () => axios(request || {}).then(response => response.data),\n    {\n      ...config,\n      fallbackData: fallbackData && {\n        status: 200,\n        statusText: 'InitialData',\n        headers: {},\n        data: fallbackData\n      }\n    }\n  ) \n}\n"
  },
  {
    "path": "examples/axios/package.json",
    "content": "{\n  \"name\": \"basic\",\n  \"version\": \"1.0.0\",\n  \"main\": \"index.js\",\n  \"license\": \"MIT\",\n  \"dependencies\": {\n    \"axios\": \"0.27.2\",\n    \"next\": \"latest\",\n    \"react\": \"latest\",\n    \"react-dom\": \"latest\",\n    \"swr\": \"latest\"\n  },\n  \"scripts\": {\n    \"dev\": \"next\",\n    \"start\": \"next start\",\n    \"build\": \"next build\"\n  }\n}\n"
  },
  {
    "path": "examples/axios/pages/[user]/[repo].js",
    "content": "import Link from 'next/link'\n\nimport useRequest from '../../libs/useRequest'\n\nexport default function Repo() {\n  const id =\n    typeof window !== 'undefined' ? window.location.pathname.slice(1) : ''\n  const { data } = useRequest(\n    id\n      ? {\n          url: '/api/data',\n          params: {\n            id\n          }\n        }\n      : null\n  )\n\n  return (\n    <div style={{ textAlign: 'center' }}>\n      <h1>{id}</h1>\n      {data ? (\n        <div>\n          <p>forks: {data.forks_count}</p>\n          <p>stars: {data.stargazers_count}</p>\n          <p>watchers: {data.watchers}</p>\n        </div>\n      ) : (\n        'loading...'\n      )}\n      <br />\n      <br />\n      <Link href=\"/\">\n        Back\n      </Link>\n    </div>\n  )\n}\n"
  },
  {
    "path": "examples/axios/pages/api/data.js",
    "content": "import axios from 'axios'\n\nconst projects = [\n  'facebook/flipper', 'vuejs/vuepress', 'rust-lang/rust', 'vercel/next.js'\n]\n\nexport default function api(req, res) {\n  if (req.query.id) {\n    // a slow endpoint for getting repo data\n    axios(`https://api.github.com/repos/${req.query.id}`)\n    .then(resp => resp.data)\n    .then(data => {\n      setTimeout(() => {\n        res.json(data)\n      }, 2000)\n    })\n    return\n  }\n  setTimeout(() => {\n    res.json(projects)\n  }, 2000)\n}\n"
  },
  {
    "path": "examples/axios/pages/index.js",
    "content": "import Link from 'next/link'\n\nimport useRequest from '../libs/useRequest'\n\nexport default function Index() {\n  const { data } = useRequest({\n    url: '/api/data'\n  })\n\n  return (\n    <div style={{ textAlign: 'center' }}>\n      <h1>Trending Projects</h1>\n      <div>\n        {data\n          ? data.map(project => (\n              <p key={project}>\n                <Link href=\"/[user]/[repo]\" as={`/${project}`}>\n                  {project}\n                </Link>\n              </p>\n            ))\n          : 'loading...'}\n      </div>\n    </div>\n  )\n}\n"
  },
  {
    "path": "examples/axios-typescript/README.md",
    "content": "# Axios TypeScript\n\n## One-Click Deploy\n\nDeploy your own SWR project with Vercel.\n\n[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?s=https://github.com/vercel/swr/tree/main/examples/axios-typescript)\n\n## How to Use\n\nDownload the example:\n\n```bash\ncurl https://codeload.github.com/vercel/swr/tar.gz/main | tar -xz --strip=2 swr-main/examples/axios-typescript\ncd axios-typescript\n```\n\nInstall it and run:\n\n```bash\nyarn\nyarn dev\n# or\nnpm install\nnpm run dev\n```\n\n## The Idea behind the Example\n\nShow how to use the basic axios along with TypeScript to type both the request object and the data received from SWR.\n"
  },
  {
    "path": "examples/axios-typescript/libs/useRequest.ts",
    "content": "import useSWR, { SWRConfiguration, SWRResponse } from 'swr'\nimport axios, { AxiosRequestConfig, AxiosResponse, AxiosError } from 'axios'\n\nexport type GetRequest = AxiosRequestConfig | null\n\ninterface Return<Data, Error>\n  extends Pick<\n    SWRResponse<AxiosResponse<Data>, AxiosError<Error>>,\n    'isValidating' | 'error' | 'mutate'\n  > {\n  data: Data | undefined\n  response: AxiosResponse<Data> | undefined\n}\n\nexport interface Config<Data = unknown, Error = unknown>\n  extends Omit<\n    SWRConfiguration<AxiosResponse<Data>, AxiosError<Error>>,\n    'fallbackData'\n  > {\n  fallbackData?: Data\n}\n\nexport default function useRequest<Data = unknown, Error = unknown>(\n  request: GetRequest,\n  { fallbackData, ...config }: Config<Data, Error> = {}\n): Return<Data, Error> {\n  const {\n    data: response,\n    error,\n    isValidating,\n    mutate\n  } = useSWR<AxiosResponse<Data>, AxiosError<Error>>(\n    request,\n    /**\n     * NOTE: Typescript thinks `request` can be `null` here, but the fetcher\n     * function is actually only called by `useSWR` when it isn't.\n     */\n    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n    () => axios.request<Data>(request!),\n    {\n      ...config,\n      fallbackData: fallbackData && {\n        status: 200,\n        statusText: 'InitialData',\n        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n        config: request!,\n        headers: {},\n        data: fallbackData\n      } as AxiosResponse<Data>\n    }\n  )\n\n  return {\n    data: response && response.data,\n    response,\n    error,\n    isValidating,\n    mutate\n  }\n}\n"
  },
  {
    "path": "examples/axios-typescript/next-env.d.ts",
    "content": "/// <reference types=\"next\" />\n/// <reference types=\"next/types/global\" />\n/// <reference types=\"next/image-types/global\" />\n\n// NOTE: This file should not be edited\n// see https://nextjs.org/docs/basic-features/typescript for more information.\n"
  },
  {
    "path": "examples/axios-typescript/package.json",
    "content": "{\n  \"name\": \"basic-typescript\",\n  \"version\": \"1.0.0\",\n  \"main\": \"index.js\",\n  \"license\": \"MIT\",\n  \"dependencies\": {\n    \"axios\": \"0.23.0\",\n    \"next\": \"latest\",\n    \"react\": \"latest\",\n    \"react-dom\": \"latest\",\n    \"swr\": \"latest\"\n  },\n  \"scripts\": {\n    \"dev\": \"next\",\n    \"start\": \"next start\",\n    \"build\": \"next build\"\n  },\n  \"devDependencies\": {\n    \"@types/node\": \"16.7.2\",\n    \"@types/react\": \"17.0.19\",\n    \"typescript\": \"4.3.5\"\n  }\n}\n"
  },
  {
    "path": "examples/axios-typescript/pages/[user]/[repo].tsx",
    "content": "import Link from 'next/link'\n\nimport useRequest from '../../libs/useRequest'\n\nexport default function Repo() {\n  const id =\n    typeof window !== 'undefined' ? window.location.pathname.slice(1) : ''\n  const { data } = useRequest<{\n    forks_count: number\n    stargazers_count: number\n    watchers: number\n  }>({\n    url: '/api/data',\n    params: { id }\n  })\n\n  return (\n    <div style={{ textAlign: 'center' }}>\n      <h1>{id}</h1>\n      {data ? (\n        <div>\n          <p>forks: {data.forks_count}</p>\n          <p>stars: {data.stargazers_count}</p>\n          <p>watchers: {data.watchers}</p>\n        </div>\n      ) : (\n        'loading...'\n      )}\n      <br />\n      <br />\n      <Link href=\"/\">Back</Link>\n    </div>\n  )\n}\n"
  },
  {
    "path": "examples/axios-typescript/pages/api/data.ts",
    "content": "import { NextApiRequest, NextApiResponse } from 'next'\nimport axios from 'axios'\n\nconst projects = [\n  'facebook/flipper',\n  'vuejs/vuepress',\n  'rust-lang/rust',\n  'vercel/next.js'\n]\n\nexport default function api(req: NextApiRequest, res: NextApiResponse) {\n  if (req.query.id) {\n    // a slow endpoint for getting repo data\n    axios(`https://api.github.com/repos/${req.query.id}`)\n      .then(response => response.data)\n      .then(data => {\n        setTimeout(() => {\n          res.json(data)\n        }, 2000)\n      })\n\n    return\n  }\n  setTimeout(() => {\n    res.json(projects)\n  }, 2000)\n}\n"
  },
  {
    "path": "examples/axios-typescript/pages/index.tsx",
    "content": "import Link from 'next/link'\n\nimport useRequest from '../libs/useRequest'\n\nexport default function Index() {\n  const { data } = useRequest<string[]>({\n    url: '/api/data'\n  })\n\n  return (\n    <div style={{ textAlign: 'center' }}>\n      <h1>Trending Projects</h1>\n      <div>\n        {data\n          ? data.map(project => (\n              <p key={project}>\n                <Link href=\"/[user]/[repo]\" as={`/${project}`}>\n                  {project}\n                </Link>\n              </p>\n            ))\n          : 'loading...'}\n      </div>\n    </div>\n  )\n}\n"
  },
  {
    "path": "examples/axios-typescript/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"es5\",\n    \"lib\": [\n      \"dom\",\n      \"dom.iterable\",\n      \"esnext\"\n    ],\n    \"allowJs\": true,\n    \"skipLibCheck\": true,\n    \"strict\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"noEmit\": true,\n    \"esModuleInterop\": true,\n    \"module\": \"esnext\",\n    \"moduleResolution\": \"node\",\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"jsx\": \"preserve\"\n  },\n  \"exclude\": [\n    \"node_modules\"\n  ],\n  \"include\": [\n    \"next-env.d.ts\",\n    \"**/*.ts\",\n    \"**/*.tsx\"\n  ]\n}\n"
  },
  {
    "path": "examples/basic/README.md",
    "content": "# Basic\n\n## One-Click Deploy\n\nDeploy your own SWR project with Vercel.\n\n[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?s=https://github.com/vercel/swr/tree/main/examples/basic)\n\n## How to Use\n\nDownload the example:\n\n```bash\ncurl https://codeload.github.com/vercel/swr/tar.gz/main | tar -xz --strip=2 swr-main/examples/basic\ncd basic\n```\n\nInstall it and run:\n\n```bash\nyarn\nyarn dev\n# or\nnpm install\nnpm run dev\n```\n\n## The Idea behind the Example\n\nShow a basic usage of SWR fetching data from an API in two different pages.\n"
  },
  {
    "path": "examples/basic/libs/fetch.js",
    "content": "export default async function fetcher(...args) {\n  const res = await fetch(...args)\n  return res.json()\n}\n"
  },
  {
    "path": "examples/basic/package.json",
    "content": "{\n  \"name\": \"basic\",\n  \"private\": true,\n  \"license\": \"MIT\",\n  \"dependencies\": {\n    \"next\": \"latest\",\n    \"react\": \"latest\",\n    \"react-dom\": \"latest\",\n    \"swr\": \"latest\"\n  },\n  \"scripts\": {\n    \"dev\": \"next\",\n    \"start\": \"next start\",\n    \"build\": \"next build\"\n  }\n}\n"
  },
  {
    "path": "examples/basic/pages/[user]/[repo].js",
    "content": "import Link from 'next/link'\nimport fetch from '../../libs/fetch'\n\nimport useSWR from 'swr'\n\nexport default function Repo() {\n  const id = typeof window !== 'undefined' ? window.location.pathname.slice(1) : ''\n  const { data } = useSWR('/api/data?id=' + id, fetch)\n\n  return (\n    <div style={{ textAlign: 'center' }}>\n      <h1>{id}</h1>\n      {\n        data ? <div>\n          <p>forks: {data.forks_count}</p>\n          <p>stars: {data.stargazers_count}</p>\n          <p>watchers: {data.watchers}</p>\n        </div> : 'loading...'\n      }\n      <br />\n      <br />\n      <Link href=\"/\">Back</Link>\n    </div>\n  )\n}\n"
  },
  {
    "path": "examples/basic/pages/api/data.js",
    "content": "const projects = [\n  'facebook/flipper', 'vuejs/vuepress', 'rust-lang/rust', 'vercel/next.js'\n]\n\nexport default function api(req, res) {\n  if (req.query.id) {\n    // a slow endpoint for getting repo data\n    fetch(`https://api.github.com/repos/${req.query.id}`)\n      .then(resp => resp.json())\n      .then(data => {\n        setTimeout(() => {\n          res.json(data)\n        }, 2000)\n      })\n\n    return\n  }\n  setTimeout(() => {\n    res.json(projects)\n  }, 2000)\n}\n"
  },
  {
    "path": "examples/basic/pages/index.js",
    "content": "import Link from 'next/link'\nimport fetch from '../libs/fetch'\n\nimport useSWR from 'swr'\n\nexport default function Index() {\n  const { data } = useSWR('/api/data', fetch)\n\n  return (\n    <div style={{ textAlign: 'center' }}>\n      <h1>Trending Projects</h1>\n      <div>\n      {\n        data ? data.map(project =>\n          <p key={project}><Link href='/[user]/[repo]' as={`/${project}`}>{project}</Link></p>\n        ) : 'loading...'\n      }\n      </div>\n    </div>\n  )\n}\n"
  },
  {
    "path": "examples/basic-typescript/README.md",
    "content": "# Basic TypeScript\n\n## One-Click Deploy\n\nDeploy your own SWR project with Vercel.\n\n[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?s=https://github.com/vercel/swr/tree/main/examples/basic-typescript)\n\n## How to Use\n\nDownload the example:\n\n```bash\ncurl https://codeload.github.com/vercel/swr/tar.gz/main | tar -xz --strip=2 swr-main/examples/basic-typescript\ncd basic-typescript\n```\n\nInstall it and run:\n\n```bash\nyarn\nyarn dev\n# or\nnpm install\nnpm run dev\n```\n\n## The Idea behind the Example\n\nShow how to use the basic example along with TypeScript to type the data received from SWR.\n"
  },
  {
    "path": "examples/basic-typescript/libs/fetch.ts",
    "content": "export default async function fetcher<JSON = any>(\n  input: RequestInfo,\n  init?: RequestInit\n): Promise<JSON> {\n  const res = await fetch(input, init)\n  return res.json()\n}\n"
  },
  {
    "path": "examples/basic-typescript/next-env.d.ts",
    "content": "/// <reference types=\"next\" />\n/// <reference types=\"next/types/global\" />\n/// <reference types=\"next/image-types/global\" />\n\n// NOTE: This file should not be edited\n// see https://nextjs.org/docs/basic-features/typescript for more information.\n"
  },
  {
    "path": "examples/basic-typescript/package.json",
    "content": "{\n  \"name\": \"basic-typescript\",\n  \"version\": \"1.0.0\",\n  \"main\": \"index.js\",\n  \"license\": \"MIT\",\n  \"dependencies\": {\n    \"next\": \"latest\",\n    \"react\": \"latest\",\n    \"react-dom\": \"latest\",\n    \"swr\": \"latest\"\n  },\n  \"scripts\": {\n    \"dev\": \"next\",\n    \"start\": \"next start\",\n    \"build\": \"next build\"\n  },\n  \"devDependencies\": {\n    \"@types/node\": \"16.7.2\",\n    \"@types/react\": \"17.0.19\",\n    \"typescript\": \"4.3.5\"\n  }\n}\n"
  },
  {
    "path": "examples/basic-typescript/pages/[user]/[repo].tsx",
    "content": "import Link from 'next/link'\nimport fetch from '../../libs/fetch'\n\nimport useSWR from 'swr'\n\nexport default function Repo() {\n  const id =\n    typeof window !== 'undefined' ? window.location.pathname.slice(1) : ''\n  const { data } = useSWR<{\n    forks_count: number\n    stargazers_count: number\n    watchers: number\n  }>('/api/data?id=' + id, fetch)\n\n  return (\n    <div style={{ textAlign: 'center' }}>\n      <h1>{id}</h1>\n      {data ? (\n        <div>\n          <p>forks: {data.forks_count}</p>\n          <p>stars: {data.stargazers_count}</p>\n          <p>watchers: {data.watchers}</p>\n        </div>\n      ) : (\n        'loading...'\n      )}\n      <br />\n      <br />\n      <Link href=\"/\">Back</Link>\n    </div>\n  )\n}\n"
  },
  {
    "path": "examples/basic-typescript/pages/api/data.ts",
    "content": "import { NextApiRequest, NextApiResponse } from 'next'\n\nconst projects = [\n  'facebook/flipper',\n  'vuejs/vuepress',\n  'rust-lang/rust',\n  'vercel/next.js'\n]\n\nexport default function api(req: NextApiRequest, res: NextApiResponse) {\n  if (req.query.id) {\n    // a slow endpoint for getting repo data\n    fetch(`https://api.github.com/repos/${req.query.id}`)\n      .then(resp => resp.json())\n      .then(data => {\n        setTimeout(() => {\n          res.json(data)\n        }, 2000)\n      })\n\n    return\n  }\n  setTimeout(() => {\n    res.json(projects)\n  }, 2000)\n}\n"
  },
  {
    "path": "examples/basic-typescript/pages/index.tsx",
    "content": "import Link from 'next/link'\nimport fetch from '../libs/fetch'\n\nimport useSWR from 'swr'\n\nexport default function HomePage() {\n  const { data } = useSWR<string[]>('/api/data', fetch)\n  const { data: data2 } = useSWR(null, fetch)\n\n  return (\n    <div style={{ textAlign: 'center' }}>\n      <h1>Trending Projects</h1>\n      {data2}\n      <div>\n        {data\n          ? data.map(project => (\n              <p key={project}>\n                <Link href=\"/[user]/[repo]\" as={`/${project}`}>\n                  {project}\n                </Link>\n              </p>\n            ))\n          : 'loading...'}\n      </div>\n    </div>\n  )\n}\n"
  },
  {
    "path": "examples/basic-typescript/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"es5\",\n    \"lib\": [\n      \"dom\",\n      \"dom.iterable\",\n      \"esnext\"\n    ],\n    \"allowJs\": true,\n    \"skipLibCheck\": true,\n    \"strict\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"noEmit\": true,\n    \"esModuleInterop\": true,\n    \"module\": \"esnext\",\n    \"moduleResolution\": \"node\",\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"jsx\": \"preserve\"\n  },\n  \"exclude\": [\n    \"node_modules\"\n  ],\n  \"include\": [\n    \"next-env.d.ts\",\n    \"**/*.ts\",\n    \"**/*.tsx\"\n  ]\n}\n"
  },
  {
    "path": "examples/focus-revalidate/README.md",
    "content": "# Focus Revalidate\n\n## One-Click Deploy\n\nDeploy your own SWR project with Vercel.\n\n[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?s=https://github.com/vercel/swr/tree/main/examples/focus-revalidate)\n\n## How to Use\n\nDownload the example:\n\n```bash\ncurl https://codeload.github.com/vercel/swr/tar.gz/main | tar -xz --strip=2 swr-main/examples/focus-revalidate\ncd focus-revalidate\n```\n\nInstall it and run:\n\n```bash\nyarn\nyarn dev\n# or\nnpm install\nnpm run dev\n```\n\n## The Idea behind the Example\n\nBasic authentication example showing how the revalidate on focus feature works and to trigger a revalidation on a per-hook call basis.\n"
  },
  {
    "path": "examples/focus-revalidate/components/button.js",
    "content": "export default function Button({ children, ...props }) {\n  return <div><button {...props}>\n    {children}\n    <style jsx>{`\n      button {\n        width: 100px;\n        height: 40px;\n        border: none;\n        color: #fff;\n        background: #03a9f4;\n        font-size: 1rem;\n        border-radius: 5px;\n      }\n    `}</style>\n  </button></div>\n}"
  },
  {
    "path": "examples/focus-revalidate/libs/auth.js",
    "content": "// mock login and logout\n\nexport function login() {\n  document.cookie = 'swr-test-token=swr;'\n}\n\nexport function logout() {\n  document.cookie = 'swr-test-token=; expires=Thu, 01 Jan 1970 00:00:01 GMT;'\n}\n"
  },
  {
    "path": "examples/focus-revalidate/libs/fetch.js",
    "content": "export default async function fetcher(...args) {\n  const res = await fetch(...args)\n  return res.json()\n}\n"
  },
  {
    "path": "examples/focus-revalidate/package.json",
    "content": "{\n  \"name\": \"focus-revalidate\",\n  \"version\": \"1.0.0\",\n  \"main\": \"index.js\",\n  \"license\": \"MIT\",\n  \"dependencies\": {\n    \"next\": \"latest\",\n    \"react\": \"latest\",\n    \"react-dom\": \"latest\",\n    \"swr\": \"latest\"\n  },\n  \"scripts\": {\n    \"dev\": \"next\",\n    \"start\": \"next start\",\n    \"build\": \"next build\"\n  }\n}\n"
  },
  {
    "path": "examples/focus-revalidate/pages/api/user.js",
    "content": "// an endpoint for getting user info\nexport default function user(req, res) {\n  if (req.cookies['swr-test-token'] === 'swr') {\n    // authorized\n    res.json({\n      loggedIn: true,\n      name: 'Shu',\n      avatar: 'https://github.com/shuding.png'\n    })\n    return\n  }\n\n  res.json({\n    loggedIn: false\n  })\n}\n"
  },
  {
    "path": "examples/focus-revalidate/pages/index.js",
    "content": "import Button from '../components/button'\nimport fetch from '../libs/fetch'\nimport { login, logout } from '../libs/auth'\n\nimport useSWR from 'swr'\n\nexport default function Index() {\n  const { data, mutate } = useSWR('/api/user', fetch)\n\n  if (!data) return <h1>loading...</h1>\n  if (data.loggedIn) {\n    return <div>\n      <h1>Welcome, {data.name}</h1>\n      <img src={data.avatar} width={80} />\n      <Button onClick={() => {\n        logout()\n        mutate() // after logging in/out, we mutate the SWR\n      }}>Logout</Button>\n    </div>\n  } else {\n    return <div>\n      <h1>Please login</h1>\n      <Button onClick={() => {\n        login()\n        mutate()\n      }}>Login</Button>\n    </div>\n  }\n}\n"
  },
  {
    "path": "examples/global-fetcher/README.md",
    "content": "# Global Fetcher\n\n## One-Click Deploy\n\nDeploy your own SWR project with Vercel.\n\n[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?s=https://github.com/vercel/swr/tree/main/examples/global-fetcher)\n\n## How to Use\n\nDownload the example:\n\n```bash\ncurl https://codeload.github.com/vercel/swr/tar.gz/main | tar -xz --strip=2 swr-main/examples/global-fetcher\ncd global-fetcher\n```\n\nInstall it and run:\n\n```bash\nyarn\nyarn dev\n# or\nnpm install\nnpm run dev\n```\n\n## The Idea behind the Example\n\nUse the `SWRConfig` provider to set up the fetcher globally instead of a per-hook call.\n"
  },
  {
    "path": "examples/global-fetcher/libs/fetch.js",
    "content": "export default async function fetcher(...args) {\n  const res = await fetch(...args)\n  return res.json()\n}\n"
  },
  {
    "path": "examples/global-fetcher/package.json",
    "content": "{\n  \"name\": \"global-fetcher\",\n  \"version\": \"1.0.0\",\n  \"main\": \"index.js\",\n  \"license\": \"MIT\",\n  \"dependencies\": {\n    \"next\": \"latest\",\n    \"react\": \"latest\",\n    \"react-dom\": \"latest\",\n    \"swr\": \"latest\"\n  },\n  \"scripts\": {\n    \"dev\": \"next\",\n    \"start\": \"next start\",\n    \"build\": \"next build\"\n  }\n}\n"
  },
  {
    "path": "examples/global-fetcher/pages/[user]/[repo].js",
    "content": "import Link from 'next/link'\n\nimport useSWR from 'swr'\n\nexport default function Repo() {\n  const id = typeof window !== 'undefined' ? window.location.pathname.slice(1) : ''\n  const { data } = useSWR('/api/data?id=' + id)\n\n  return (\n    <div style={{ textAlign: 'center' }}>\n      <h1>{id}</h1>\n      {\n        data ? <div>\n          <p>forks: {data.forks_count}</p>\n          <p>stars: {data.stargazers_count}</p>\n          <p>watchers: {data.watchers}</p>\n        </div> : 'loading...'\n      }\n      <br />\n      <br />\n      <Link href=\"/\">Back</Link>\n    </div>\n  )\n}\n"
  },
  {
    "path": "examples/global-fetcher/pages/_app.js",
    "content": "import React from 'react'\nimport App from 'next/app'\nimport { SWRConfig } from 'swr'\nimport fetch from '../libs/fetch.js';\n\nexport default class MyApp extends App {\n  render() {\n    const { Component, pageProps } = this.props\n    return <SWRConfig\n      value={{\n        fetcher: fetch\n      }}\n    >\n      <Component {...pageProps} />\n    </SWRConfig>\n  }\n}\n"
  },
  {
    "path": "examples/global-fetcher/pages/api/data.js",
    "content": "const projects = [\n  'facebook/flipper', 'vuejs/vuepress', 'rust-lang/rust', 'vercel/next.js'\n]\n\nexport default function api(req, res) {\n  if (req.query.id) {\n    // a slow endpoint for getting repo data\n    fetch(`https://api.github.com/repos/${req.query.id}`)\n      .then(resp => resp.json())\n      .then(data => {\n        setTimeout(() => {\n          res.json(data)\n        }, 2000)\n      })\n\n    return\n  }\n  setTimeout(() => {\n    res.json(projects)\n  }, 2000)\n}\n"
  },
  {
    "path": "examples/global-fetcher/pages/index.js",
    "content": "import Link from 'next/link'\n\nimport useSWR from 'swr'\n\nexport default function Index() {\n  const { data } = useSWR('/api/data')\n\n  return (\n    <div style={{ textAlign: 'center' }}>\n      <h1>Trending Projects</h1>\n      <div>\n        {data\n          ? data.map(project => (\n              <p key={project}>\n                <Link href=\"/[user]/[repo]\" as={`/${project}`}>\n                  {project}\n                </Link>\n              </p>\n            ))\n          : 'loading...'}\n      </div>\n    </div>\n  )\n}\n"
  },
  {
    "path": "examples/infinite/README.md",
    "content": "# useSWRInfinite\n\n## One-Click Deploy\n\nDeploy your own SWR project with Vercel.\n\n[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?s=https://github.com/vercel/swr/tree/main/examples/infinite)\n\n## How to Use\n\nDownload the example:\n\n```bash\ncurl https://codeload.github.com/vercel/swr/tar.gz/main | tar -xz --strip=2 swr-main/examples/infinite\ncd basic\n```\n\nInstall it and run:\n\n```bash\nyarn\nyarn dev\n# or\nnpm install\nnpm run dev\n```\n\n## The Idea behind the Example\n\nShow usage of useSWRInfinite with load more data button\n"
  },
  {
    "path": "examples/infinite/libs/fetch.js",
    "content": "export default async function fetcher(...args) {\n  const res = await fetch(...args)\n  return res.json()\n}\n"
  },
  {
    "path": "examples/infinite/package.json",
    "content": "{\n  \"name\": \"infinite\",\n  \"version\": \"1.0.0\",\n  \"main\": \"index.js\",\n  \"license\": \"MIT\",\n  \"dependencies\": {\n    \"next\": \"latest\",\n    \"react\": \"latest\",\n    \"react-dom\": \"latest\",\n    \"swr\": \"latest\"\n  },\n  \"scripts\": {\n    \"dev\": \"next\",\n    \"start\": \"next start\",\n    \"build\": \"next build\"\n  }\n}\n"
  },
  {
    "path": "examples/infinite/pages/index.js",
    "content": "import { useState } from 'react'\nimport useSWRInfinite from 'swr/infinite'\n\nimport fetch from '../libs/fetch'\n\nconst PAGE_SIZE = 6\n\nexport default function App() {\n  const [repo, setRepo] = useState('reactjs/react-a11y')\n  const [val, setVal] = useState(repo)\n\n  const { data, error, mutate, size, setSize, isValidating } = useSWRInfinite(\n    (index) =>\n      `https://api.github.com/repos/${repo}/issues?per_page=${PAGE_SIZE}&page=${\n        index + 1\n      }`,\n    fetch\n  )\n\n  const issues = data ? [].concat(...data) : []\n  const isLoadingInitialData = !data && !error\n  const isLoadingMore =\n    isLoadingInitialData ||\n    (size > 0 && data && typeof data[size - 1] === 'undefined')\n  const isEmpty = data?.[0]?.length === 0\n  const isReachingEnd =\n    isEmpty || (data && data[data.length - 1]?.length < PAGE_SIZE)\n  const isRefreshing = isValidating && data && data.length === size\n\n  return (\n    <div style={{ fontFamily: 'sans-serif' }}>\n      <input\n        value={val}\n        onChange={(e) => setVal(e.target.value)}\n        placeholder=\"reactjs/react-a11y\"\n      />\n      <button\n        onClick={() => {\n          setRepo(val)\n          setSize(1)\n        }}\n      >\n        load issues\n      </button>\n      <p>\n        showing {size} page(s) of {isLoadingMore ? '...' : issues.length}{' '}\n        issue(s){' '}\n        <button\n          disabled={isLoadingMore || isReachingEnd}\n          onClick={() => setSize(size + 1)}\n        >\n          {isLoadingMore\n            ? 'loading...'\n            : isReachingEnd\n            ? 'no more issues'\n            : 'load more'}\n        </button>\n        <button disabled={isRefreshing} onClick={() => mutate()}>\n          {isRefreshing ? 'refreshing...' : 'refresh'}\n        </button>\n        <button disabled={!size} onClick={() => setSize(0)}>\n          clear\n        </button>\n      </p>\n      {isEmpty ? <p>Yay, no issues found.</p> : null}\n      {issues.map((issue) => {\n        return (\n          <p key={issue.id} style={{ margin: '6px 0' }}>\n            - {issue.title}\n          </p>\n        )\n      })}\n    </div>\n  )\n}\n"
  },
  {
    "path": "examples/infinite-scroll/README.md",
    "content": "# useSWRInfinite with scroll based on IntersectionObserver\n\n## One-Click Deploy\n\nDeploy your own SWR project with Vercel.\n\n[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?s=https://github.com/vercel/swr/tree/main/examples/infinite-scroll)\n\n## How to Use\n\nDownload the example:\n\n```bash\ncurl https://codeload.github.com/vercel/swr/tar.gz/main | tar -xz --strip=2 swr-main/examples/infinite-scroll\ncd infinite-scroll\n```\n\nInstall it and run:\n\n```bash\nyarn\nyarn dev\n# or\nnpm install\nnpm run dev\n```\n\n## The Idea behind the Example\n\nShow usage of useSWRInfinite with infinite scroll based on IntersectionObserver\n"
  },
  {
    "path": "examples/infinite-scroll/hooks/useOnScreen.js",
    "content": "import { useState, useEffect } from 'react'\n\nexport default function useOnScreen(ref) {\n  const [isIntersecting, setIntersecting] = useState(false)\n\n  useEffect(() => {\n    if (!ref.current) return\n\n    const observer = new IntersectionObserver(([entry]) =>\n      setIntersecting(entry.isIntersecting)\n    )\n\n    observer.observe(ref.current)\n    // Remove the observer as soon as the component is unmounted\n    return () => {\n      observer.disconnect()\n    }\n  }, [])\n\n  return isIntersecting\n}\n"
  },
  {
    "path": "examples/infinite-scroll/libs/fetch.js",
    "content": "export default async function fetcher(...args) {\n  const res = await fetch(...args)\n  return res.json()\n}\n"
  },
  {
    "path": "examples/infinite-scroll/package.json",
    "content": "{\n  \"name\": \"infinite-scroll\",\n  \"version\": \"1.0.0\",\n  \"main\": \"index.js\",\n  \"license\": \"MIT\",\n  \"dependencies\": {\n    \"next\": \"latest\",\n    \"react\": \"latest\",\n    \"react-dom\": \"latest\",\n    \"swr\": \"latest\"\n  },\n  \"scripts\": {\n    \"dev\": \"next\",\n    \"start\": \"next start\",\n    \"build\": \"next build\"\n  }\n}\n"
  },
  {
    "path": "examples/infinite-scroll/pages/index.js",
    "content": "import useSWRInfinite from 'swr/infinite'\nimport { useState, useRef, useEffect } from 'react'\n\nimport fetcher from '../libs/fetch'\nimport useOnScreen from '../hooks/useOnScreen'\n\nconst PAGE_SIZE = 6\n\nconst getKey = (pageIndex, previousPageData, repo, pageSize) => {\n  if (previousPageData && !previousPageData.length) return null // reached the end\n\n  return `https://api.github.com/repos/${repo}/issues?per_page=${pageSize}&page=${\n    pageIndex + 1\n  }`\n}\n\nexport default function App() {\n  const ref = useRef()\n  const [repo, setRepo] = useState('facebook/react')\n  const [val, setVal] = useState(repo)\n\n  const isVisible = useOnScreen(ref)\n\n  const { data, error, mutate, size, setSize, isValidating } = useSWRInfinite(\n    (...args) => getKey(...args, repo, PAGE_SIZE),\n    fetcher\n  )\n\n  const issues = data ? [].concat(...data) : []\n  const isLoadingInitialData = !data && !error\n  const isLoadingMore =\n    isLoadingInitialData ||\n    (size > 0 && data && typeof data[size - 1] === 'undefined')\n  const isEmpty = data?.[0]?.length === 0\n  const isReachingEnd = size === PAGE_SIZE\n  const isRefreshing = isValidating && data && data.length === size\n\n  useEffect(() => {\n    if (isVisible && !isReachingEnd && !isRefreshing) {\n      setSize(size + 1)\n    }\n  }, [isVisible, isRefreshing])\n\n  return (\n    <div style={{ fontFamily: 'sans-serif' }}>\n      <input\n        value={val}\n        onChange={(e) => setVal(e.target.value)}\n        placeholder=\"facebook/react\"\n      />\n      <button\n        onClick={() => {\n          setRepo(val)\n          setSize(1)\n        }}\n      >\n        load issues\n      </button>\n      <p>\n        showing {size} page(s) of {isLoadingMore ? '...' : issues.length}{' '}\n        issue(s){' '}\n        <button disabled={isRefreshing} onClick={() => mutate()}>\n          {isRefreshing ? 'refreshing...' : 'refresh'}\n        </button>\n        <button disabled={!size} onClick={() => setSize(0)}>\n          clear\n        </button>\n      </p>\n      {isEmpty ? <p>Yay, no issues found.</p> : null}\n      {issues.map((issue) => {\n        return (\n          <p key={issue.id} style={{ margin: '6px 0', height: 50 }}>\n            - {issue.title}\n          </p>\n        )\n      })}\n      <div ref={ref}>\n        {isLoadingMore ? 'loading...' : isReachingEnd ? 'no more issues' : ''}\n      </div>\n    </div>\n  )\n}\n"
  },
  {
    "path": "examples/local-state-sharing/README.md",
    "content": "# Basic\n\n## One-Click Deploy\n\nDeploy your own SWR project with Vercel.\n\n[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?s=https://github.com/vercel/swr/tree/main/examples/local-state-sharing)\n\n## How to Use\n\nDownload the example:\n\n```bash\ncurl https://codeload.github.com/vercel/swr/tar.gz/main | tar -xz --strip=2 swr-main/examples/local-state-sharing\ncd local-state-sharing\n```\n\nInstall it and run:\n\n```bash\nyarn\nyarn dev\n# or\nnpm install\nnpm run dev\n```\n\n## The Idea behind the Example\n\nShow how to share local state between React components using SWR.\n"
  },
  {
    "path": "examples/local-state-sharing/libs/store.js",
    "content": "const initialStore = { name: \"john\" };\n\nexport default initialStore;\n"
  },
  {
    "path": "examples/local-state-sharing/package.json",
    "content": "{\n  \"name\": \"local-state-sharing\",\n  \"version\": \"1.0.0\",\n  \"main\": \"index.js\",\n  \"license\": \"MIT\",\n  \"dependencies\": {\n    \"react\": \"latest\",\n    \"react-dom\": \"latest\",\n    \"next\": \"latest\",\n    \"swr\": \"latest\"\n  },\n  \"scripts\": {\n    \"dev\": \"next\",\n    \"start\": \"next start\",\n    \"build\": \"next build\"\n  }\n}\n"
  },
  {
    "path": "examples/local-state-sharing/pages/index.js",
    "content": "import { useState } from \"react\"\nimport initialStore from \"../libs/store\"\nimport useSWR, { mutate } from \"swr\"\n\nfunction Profile() {\n  const { data } = useSWR(\"globalState\", { fallbackData: initialStore })\n  const [value, updateValue] = useState((data || {}).name)\n  if (!data) {\n    return null\n  }\n  return (\n    <div>\n      <h1>My name is {data.name}.</h1>\n      <input\n        value={value}\n        onChange={e => updateValue(e.target.value)}\n        style={{ width: 200, marginRight: 8 }}\n      />\n      <button\n        type=\"button\"\n        onClick={() => {\n          mutate(\"globalState\", { ...data, name: value }, false)\n        }}\n      >\n        Uppercase my name!\n      </button>\n    </div>\n  )\n}\n\nfunction Other() {\n  const { data } = useSWR(\"globalState\", { fallbackData: initialStore })\n  if (!data) {\n    return null\n  }\n  return (\n    <div style={{ border: \"1px solid #ddd\", marginTop: 30, padding: 20 }}>\n      <h1>\n        Another Component: <br />\n        My name is {data.name}.\n      </h1>\n    </div>\n  )\n}\n\nexport default function Index() {\n  return (\n    <div style={{ padding: 40 }}>\n      useSWR can share state between components:\n      <Profile />\n      <Other />\n    </div>\n  )\n}\n"
  },
  {
    "path": "examples/optimistic-ui/README.md",
    "content": "# Optimistic UI\n\n## One-Click Deploy\n\nDeploy your own SWR project with Vercel.\n\n[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?s=https://github.com/vercel/swr/tree/main/examples/optimistic-ui)\n\n## How to Use\n\nDownload the example:\n\n```bash\ncurl https://codeload.github.com/vercel/swr/tar.gz/main | tar -xz --strip=2 swr-main/examples/optimistic-ui\ncd optimistic-ui\n```\n\nInstall it and run:\n\n```bash\nyarn\nyarn dev\n# or\nnpm install\nnpm run dev\n```\n\n## The Idea behind the Example\n\nExample of how to use SWR to implement an Optimistic UI pattern where we mutate the cached data immediately and then trigger a revalidation with the API.\n"
  },
  {
    "path": "examples/optimistic-ui/libs/fetch.js",
    "content": "export default async function fetcher(...args) {\n  const res = await fetch(...args)\n  if (!res.ok) throw new Error('Failed to fetch')\n  return res.json()\n}\n"
  },
  {
    "path": "examples/optimistic-ui/package.json",
    "content": "{\n  \"name\": \"optimistic-ui\",\n  \"version\": \"1.0.0\",\n  \"main\": \"index.js\",\n  \"license\": \"MIT\",\n  \"dependencies\": {\n    \"next\": \"latest\",\n    \"react\": \"latest\",\n    \"react-dom\": \"latest\",\n    \"swr\": \"latest\"\n  },\n  \"scripts\": {\n    \"dev\": \"next\",\n    \"start\": \"next start\",\n    \"build\": \"next build\"\n  }\n}\n"
  },
  {
    "path": "examples/optimistic-ui/pages/_app.js",
    "content": "import '../styles.css'\n\nexport default function App({ Component, pageProps }) {\n  return <Component {...pageProps} />\n}\n"
  },
  {
    "path": "examples/optimistic-ui/pages/api/todos.js",
    "content": "let todos = []\nconst delay = () => new Promise(res => setTimeout(() => res(), 1000))\n\nasync function getTodos() {\n  await delay()\n  return todos.sort((a, b) => (a.text < b.text ? -1 : 1))\n}\n\nasync function addTodo(todo) {\n  await delay()\n  // Sometimes it will fail, this will cause a regression on the UI\n  if (Math.random() < 0.2 || !todo.text)\n    throw new Error('Failed to add new item!')\n  todo.text = todo.text.charAt(0).toUpperCase() + todo.text.slice(1)\n  todos = [...todos, todo]\n  return todo\n}\n\nexport default async function api(req, res) {\n  try {\n    if (req.method === 'POST') {\n      const body = JSON.parse(req.body)\n      return res.json(await addTodo(body))\n    }\n\n    return res.json(await getTodos())\n  } catch (err) {\n    return res.status(500).json({ error: err.message })\n  }\n}\n"
  },
  {
    "path": "examples/optimistic-ui/pages/index.js",
    "content": "import useSWR from 'swr'\nimport React, { useState } from 'react'\n\nimport fetch from '../libs/fetch'\n\nexport default function App() {\n  const [text, setText] = useState('')\n  const { data, mutate } = useSWR('/api/todos', fetch)\n\n  const [state, setState] = useState(<span className=\"info\">&nbsp;</span>)\n\n  return (\n    <div>\n      {/* <Toaster toastOptions={{ position: 'bottom-center' }} /> */}\n      <h1>Optimistic UI with SWR</h1>\n\n      <p className=\"note\">\n        <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\">\n          <path d=\"M12 2c5.514 0 10 4.486 10 10s-4.486 10-10 10-10-4.486-10-10 4.486-10 10-10zm0-2c-6.627 0-12 5.373-12 12s5.373 12 12 12 12-5.373 12-12-5.373-12-12-12zm1 18h-2v-8h2v8zm-1-12.25c.69 0 1.25.56 1.25 1.25s-.56 1.25-1.25 1.25-1.25-.56-1.25-1.25.56-1.25 1.25-1.25z\" />\n        </svg>\n        This application optimistically updates the data, while revalidating in\n        the background. The <code>POST</code> API auto capitializes the data,\n        and only returns the new added one instead of the full list. And the{' '}\n        <code>GET</code> API returns the full list in order.\n      </p>\n\n      <form onSubmit={ev => ev.preventDefault()}>\n        <input\n          value={text}\n          onChange={e => setText(e.target.value)}\n          placeholder=\"Add your to-do here...\"\n          autoFocus\n        />\n        <button\n          type=\"submit\"\n          onClick={async () => {\n            setText('')\n            setState(\n              <span className=\"info\">Showing optimistic data, mutating...</span>\n            )\n\n            const newTodo = {\n              id: Date.now(),\n              text\n            }\n\n            try {\n              // Update the local state immediately and fire the\n              // request. Since the API will return the updated\n              // data, there is no need to start a new revalidation\n              // and we can directly populate the cache.\n              await mutate(\n                fetch('/api/todos', {\n                  method: 'POST',\n                  body: JSON.stringify(newTodo)\n                }),\n                {\n                  optimisticData: [...data, newTodo],\n                  rollbackOnError: true,\n                  populateCache: newItem => {\n                    setState(\n                      <span className=\"success\">\n                        Successfully mutated the resource and populated cache.\n                        Revalidating...\n                      </span>\n                    )\n\n                    return [...data, newItem]\n                  },\n                  revalidate: true\n                }\n              )\n              setState(<span className=\"info\">Revalidated the resource.</span>)\n            } catch (e) {\n              // If the API errors, the original data will be\n              // rolled back by SWR automatically.\n              setState(\n                <span className=\"error\">\n                  Failed to mutate. Rolled back to previous state and\n                  revalidated the resource.\n                </span>\n              )\n            }\n          }}\n        >\n          Add\n        </button>\n      </form>\n\n      {state}\n\n      <ul>\n        {data ? (\n          data.length ? (\n            data.map(todo => {\n              return <li key={todo.id}>{todo.text}</li>\n            })\n          ) : (\n            <i>\n              No todos yet. Try adding lowercased \"banana\" and \"apple\" to the\n              list.\n            </i>\n          )\n        ) : (\n          <i>Loading...</i>\n        )}\n      </ul>\n    </div>\n  )\n}\n"
  },
  {
    "path": "examples/optimistic-ui/styles.css",
    "content": "html {\n  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen,\n    Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;\n  text-align: center;\n}\n\nbody {\n  max-width: 600px;\n  margin: auto;\n}\n\nh1 {\n  margin-top: 1em;\n}\n\n.note {\n  text-align: left;\n  font-size: 0.9em;\n  line-height: 1.5;\n  color: #666;\n}\n\n.note svg {\n  margin-right: 0.5em;\n  vertical-align: -2px;\n  width: 14px;\n  height: 14px;\n  margin-right: 5px;\n}\n\nform {\n  display: flex;\n  margin: 8px 0;\n  gap: 8px;\n}\n\ninput {\n  flex: 1;\n}\n\ninput,\nbutton {\n  font-size: 16px;\n  padding: 6px 5px;\n}\n\ncode {\n  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,\n    Liberation Mono, Courier New, monospace;\n  font-feature-settings: 'rlig' 1, 'calt' 1, 'ss01' 1;\n  background-color: #eee;\n  padding: 1px 3px;\n  border-radius: 2px;\n}\n\nul {\n  text-align: left;\n  list-style: none;\n  padding: 0;\n}\n\nli {\n  margin: 8px 0;\n  padding: 10px;\n  border-radius: 4px;\n  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.12), 0 0 0 1px #ededed;\n}\n\ni {\n  color: #999;\n}\n\n.info,\n.success,\n.error {\n  display: block;\n  text-align: left;\n  padding: 6px 0;\n  font-size: 0.9em;\n  opacity: 0.9;\n}\n\n.info {\n  color: #666;\n}\n.success {\n  color: #4caf50;\n}\n.error {\n  color: #f44336;\n}\n"
  },
  {
    "path": "examples/optimistic-ui-immer/README.md",
    "content": "# Optimistic UI with Immer\n\n## One-Click Deploy\n\nDeploy your own SWR project with Vercel.\n\n[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?s=https://github.com/vercel/swr/tree/main/examples/optimistic-ui-immer)\n\n## How to Use\n\nDownload the example:\n\n```bash\ncurl https://codeload.github.com/vercel/swr/tar.gz/main | tar -xz --strip=2 swr-main/examples/optimistic-ui-immer\ncd optimistic-ui-immer\n```\n\nInstall it and run:\n\n```bash\nyarn\nyarn dev\n# or\nnpm install\nnpm run dev\n```\n\n## The Idea behind the Example\n\nExample of how to use SWR and Immer to implement an Optimistic UI pattern where we mutate the cached data immediately and then trigger a revalidation with the API.\n"
  },
  {
    "path": "examples/optimistic-ui-immer/libs/fetch.js",
    "content": "export default async function fetcher(...args) {\n  const res = await fetch(...args)\n  return res.json()\n}\n"
  },
  {
    "path": "examples/optimistic-ui-immer/package.json",
    "content": "{\n  \"name\": \"optimistic-ui-immer\",\n  \"version\": \"1.0.0\",\n  \"main\": \"index.js\",\n  \"license\": \"MIT\",\n  \"dependencies\": {\n    \"immer\": \"9.0.5\",\n    \"next\": \"latest\",\n    \"react\": \"latest\",\n    \"react-dom\": \"latest\",\n    \"swr\": \"latest\"\n  },\n  \"scripts\": {\n    \"dev\": \"next\",\n    \"start\": \"next start\",\n    \"build\": \"next build\"\n  }\n}\n"
  },
  {
    "path": "examples/optimistic-ui-immer/pages/api/data.js",
    "content": "const data = []\n\nfunction shouldFail() {\n  return Math.random() > 0.8\n}\n\nexport default function api(req, res) {\n  if (req.method === 'POST') {\n    const body = JSON.parse(req.body)\n    // sometimes it will fail, and this will cause a regression in the UI\n    if (!shouldFail()) {\n      data.push(body.text);\n    }\n    res.json(data)\n    return\n  }\n\n  setTimeout(() => {\n    res.json(data)\n  }, 2000)\n}\n\n"
  },
  {
    "path": "examples/optimistic-ui-immer/pages/index.js",
    "content": "import React from 'react'\nimport fetch from '../libs/fetch'\n\nimport useSWR, { mutate } from 'swr'\nimport produce from \"immer\"\n\nexport default function Index() {\n  const [text, setText] = React.useState('');\n  const { data } = useSWR('/api/data', fetch)\n\n  async function handleSubmit(event) {\n    event.preventDefault()\n    // Call mutate to optimistically update the UI.\n    // We use Immer produce to allow us to perform an immutable change\n    // while coding it as a normal mutation of the same object.\n    mutate(\"/api/data\", produce(draftData => {\n      draftData.push(text)\n    }), false)\n    // Then we send the request to the API and let mutate\n    // update the data with the API response.\n    // Our action may fail in the API function, and the response differ\n    // from what was optimistically updated, in that case, the UI will be\n    // changed to match the API response.\n    // The fetch could also fail, in that case, the UI will\n    // be in an incorrect state until the next successful fetch.\n    mutate('/api/data', await fetch('/api/data', {\n      method: 'POST',\n      body: JSON.stringify({ text })\n    }))\n    setText('')\n  }\n\n  return <div>\n    <form onSubmit={handleSubmit}>\n      <input\n        type=\"text\"\n        onChange={event => setText(event.target.value)}\n        value={text}\n      />\n      <button>Create</button>\n    </form>\n    <ul>\n      {data ? data.map(datum => <li key={datum}>{datum}</li>) : 'loading...'}\n    </ul>\n  </div>\n}\n"
  },
  {
    "path": "examples/prefetch-preload/README.md",
    "content": "# Prefetch & Preload\n\n## One-Click Deploy\n\nDeploy your own SWR project with Vercel.\n\n[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?s=https://github.com/vercel/swr/tree/main/examples/prefetch-preload)\n\n## How to Use\n\nDownload the example:\n\n```bash\ncurl https://codeload.github.com/vercel/swr/tar.gz/main | tar -xz --strip=2 swr-main/examples/prefetch-preload\ncd prefetch-preload\n```\n\nInstall it and run:\n\n```bash\nyarn\nyarn dev\n# or\nnpm install\nnpm run dev\n```\n\n## The Idea behind the Example\n\nThis example shows multiple ways to prefetch data to be used by SWR later.\n\n- Use a `<link preload>` to get the browser to load the data while rendering the HTML\n- If in a browser, run the fetch + mutate outside the component\n- After rendering use an effect in React to prefetch the next page's data\n- When the user moves the mouse over a link trigger a fetch + mutate for the next page\n\nIn the real world you would not necessarily use all of them at the same time but one or more combined to give the best UX possible.\n"
  },
  {
    "path": "examples/prefetch-preload/libs/fetch.js",
    "content": "export default async function fetcher(...args) {\n  const res = await fetch(...args)\n  return res.json()\n}\n"
  },
  {
    "path": "examples/prefetch-preload/package.json",
    "content": "{\n  \"name\": \"prefetch-preload\",\n  \"version\": \"1.0.0\",\n  \"main\": \"index.js\",\n  \"license\": \"MIT\",\n  \"dependencies\": {\n    \"next\": \"latest\",\n    \"react\": \"latest\",\n    \"react-dom\": \"latest\",\n    \"swr\": \"latest\"\n  },\n  \"scripts\": {\n    \"dev\": \"next\",\n    \"start\": \"next start\",\n    \"build\": \"next build\"\n  }\n}\n"
  },
  {
    "path": "examples/prefetch-preload/pages/[user]/[repo].js",
    "content": "import Head from \"next/head\"\nimport Link from 'next/link'\nimport React from 'react'\nimport fetch from '../../libs/fetch'\n\nimport useSWR, { mutate } from 'swr'\n\nfunction prefetchParent() {\n  return fetch('/api/data')\n    .then(projects => mutate('/api/data', projects, false))\n}\n\n// if we are on the browser trigger a prefetch as soon as possible\nif (typeof window !== 'undefined') prefetchParent()\n\nexport default function Repo() {\n  const id = typeof window !== 'undefined' ? window.location.pathname.slice(1) : ''\n  const { data } = useSWR('/api/data?id=' + id, fetch)\n\n  React.useEffect(() => {\n    prefetchParent()\n  }, [])\n\n  function handleMouseEnter() {\n    prefetchParent()\n  }\n\n  return (\n    <>\n      <Head>\n        {/* This will tell the browser to preload the data for our page */}\n        {id && <link preload={`/api/data?id=${id}`} as=\"fetch\" crossOrigin=\"anonymous\" />}\n      </Head>\n      <div style={{ textAlign: 'center' }}>\n        <h1>{id}</h1>\n        {\n          data ? <div>\n            <p>forks: {data.forks_count}</p>\n            <p>stars: {data.stargazers_count}</p>\n            <p>watchers: {data.watchers}</p>\n          </div> : 'loading...'\n        }\n        <br />\n        <br />\n        <Link href=\"/\" onMouseEnter={handleMouseEnter}>Back</Link>\n      </div>\n    </>\n  )\n}\n"
  },
  {
    "path": "examples/prefetch-preload/pages/api/data.js",
    "content": "const projects = [\n  'facebook/flipper', 'vuejs/vuepress', 'rust-lang/rust', 'vercel/next.js'\n]\n\nexport default function api(req, res) {\n  if (req.query.id) {\n    // a slow endpoint for getting repo data\n    fetch(`https://api.github.com/repos/${req.query.id}`)\n      .then(resp => resp.json())\n      .then(data => {\n        setTimeout(() => {\n          res.json(data)\n        }, 2000)\n      })\n\n    return\n  }\n  setTimeout(() => {\n    res.json(projects)\n  }, 2000)\n}\n"
  },
  {
    "path": "examples/prefetch-preload/pages/index.js",
    "content": "import React from 'react'\nimport Head from \"next/head\";\nimport Link from 'next/link'\nimport fetch from '../libs/fetch'\n\nimport useSWR, { mutate } from 'swr'\n\nfunction prefetchData() {\n  return fetch('/api/data')\n    .then(data => {\n      mutate('/api/data', data, false)\n      return data\n    })\n}\n\nfunction prefetchItem(project) {\n  return fetch(`https://api.github.com/repos/${project}`).then(data => {\n    mutate(`/api/data?id=${project}`, data, false)\n    return data\n  })\n}\n\nfunction prefetchWithProjects() {\n  return prefetchData()\n    .then(projects => Promise.all(projects.map(prefetchItem)))\n}\n\n// if we are on the browser trigger a prefetch as soon as possible\nif (typeof window !== 'undefined') prefetchWithProjects()\n\nexport default function Index() {\n  const { data } = useSWR('/api/data', fetch)\n\n  // This effect will fetch all projects after mounting\n  React.useEffect(() => {\n    if (!data) return\n    if (data.length === 0) return\n    data.forEach(prefetchItem)\n  }, [data]);\n\n  // With this handler, you could prefetch the data for a specific\n  // project the moment the user moves the mouse over the link\n  function handleMouseEnter(event) {\n    // In our case, we could get the ID from the href so we use that\n    prefetchItem(event.target.getAttribute(\"href\").slice(1))\n  }\n\n  return (\n    <>\n      <Head>\n        {/* This will tell the browser to preload the data for our page */}\n        <link preload=\"/api/data\" as=\"fetch\" crossOrigin=\"anonymous\" />\n      </Head>\n      <div style={{ textAlign: 'center' }}>\n        <h1>Trending Projects</h1>\n        <div>\n        {\n          data ? data.map(project =>\n            <p key={project}>\n              <Link href='/[user]/[repo]' as={`/${project}`} onMouseEnter={handleMouseEnter}>\n                {project}\n              </Link>\n            </p>\n          ) : 'loading...'\n        }\n        </div>\n      </div>\n    </>\n  )\n}\n"
  },
  {
    "path": "examples/refetch-interval/README.md",
    "content": "# Refetch Interval\n\n## One-Click Deploy\n\nDeploy your own SWR project with Vercel Now.\n\n[![Deploy with Vercel Now](https://vercel.com/button)](https://vercel.com/new/clone?s=https://github.com/vercel/swr/tree/main/examples/refetch-interval)\n\n## How to Use\n\nDownload the example:\n\n```bash\ncurl https://codeload.github.com/vercel/swr/tar.gz/main | tar -xz --strip=2 swr-main/examples/refetch-interval\ncd refetch-interval\n```\n\nInstall it and run:\n\n```bash\nyarn\nyarn dev\n# or\nnpm install\nnpm run dev\n```\n\n## The Idea behind the Example\n\nShow how to make SWR fetch the API again in an interval automatically to ensure the data is up-to-date.\n"
  },
  {
    "path": "examples/refetch-interval/components/button.js",
    "content": "export default function Button({ children, ...props }) {\n  return <div><button {...props}>\n    {children}\n    <style jsx>{`\n      button {\n        width: 100px;\n        height: 40px;\n        border: none;\n        color: #fff;\n        background: #03a9f4;\n        font-size: 1rem;\n        border-radius: 5px;\n      }\n    `}</style>\n  </button></div>\n}"
  },
  {
    "path": "examples/refetch-interval/libs/fetch.js",
    "content": "export default async function fetcher(...args) {\n  const res = await fetch(...args)\n  return res.json()\n}\n"
  },
  {
    "path": "examples/refetch-interval/package.json",
    "content": "{\n  \"name\": \"refetch-interval\",\n  \"version\": \"1.0.0\",\n  \"main\": \"index.js\",\n  \"license\": \"MIT\",\n  \"dependencies\": {\n    \"next\": \"latest\",\n    \"react\": \"latest\",\n    \"react-dom\": \"latest\",\n    \"swr\": \"latest\"\n  },\n  \"scripts\": {\n    \"dev\": \"next\",\n    \"start\": \"next start\",\n    \"build\": \"next build\"\n  }\n}\n"
  },
  {
    "path": "examples/refetch-interval/pages/api/data.js",
    "content": "// an simple endpoint for getting current list\nlet list = []\n\nexport default function api(req, res) {\n  if (req.query.add) {\n    list.push(req.query.add)\n  } else if (req.query.clear) {\n    list = []\n  }\n  res.json(list)\n}\n"
  },
  {
    "path": "examples/refetch-interval/pages/index.js",
    "content": "import { useState } from 'react'\nimport Button from '../components/button'\nimport fetch from '../libs/fetch'\n\nimport useSWR from 'swr'\n\nexport default function Index() {\n  const { data, mutate } = useSWR('/api/data', fetch, {\n    // revalidate the data per second\n    refreshInterval: 1000\n  })\n  const [value, setValue] = useState('')\n\n  if (!data) return <h1>loading...</h1>\n\n  return (\n    <div>\n      <h1>Refetch Interval (1s)</h1>\n      <h2>Todo List</h2>\n      <form onSubmit={async ev => {\n        ev.preventDefault()\n        setValue('')\n        await fetch(`/api/data?add=${value}`)\n        mutate()\n      }}>\n        <input placeholder='enter something' value={value} onChange={ev => setValue(ev.target.value)} />\n      </form>\n      <ul>\n        {data.map(item => <li key={item}>{item}</li>)}\n      </ul>\n      <Button onClick={async () => {\n        await fetch(`/api/data?clear=1`)\n        mutate()\n      }}>Clear All</Button>\n    </div>\n  )\n}\n"
  },
  {
    "path": "examples/server-render/README.md",
    "content": "# Server Render\n\n## One-Click Deploy\n\nDeploy your own SWR project with Vercel.\n\n[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?s=https://github.com/vercel/swr/tree/main/examples/server-render)\n\n## How to Use\n\nDownload the example:\n\n```bash\ncurl https://codeload.github.com/vercel/swr/tar.gz/main | tar -xz --strip=2 swr-main/examples/server-render\ncd server-render\n```\n\nInstall it and run:\n\n```bash\nyarn\nyarn dev\n# or\nnpm install\nnpm run dev\n```\n\n## The Idea behind the Example\n\nThis example shows how to combine Next.js getServerSideProps with the SWR `fallbackData` option to support Server-Side Rendering.\n\nThe application will fetch the data server-side and then receive it as props, that data will be passed as `fallbackData` to SWR, once the application starts client-side SWR will revalidate it against the API and update the DOM, if it's required, with the new data.\n"
  },
  {
    "path": "examples/server-render/libs/fetcher.js",
    "content": "export default async function fetcher(...args) {\n  const res = await fetch(...args)\n  return res.json()\n}\n"
  },
  {
    "path": "examples/server-render/package.json",
    "content": "{\n  \"name\": \"server-render\",\n  \"version\": \"1.0.0\",\n  \"main\": \"index.js\",\n  \"license\": \"MIT\",\n  \"dependencies\": {\n    \"next\": \"latest\",\n    \"react\": \"latest\",\n    \"react-dom\": \"latest\",\n    \"swr\": \"latest\"\n  },\n  \"scripts\": {\n    \"dev\": \"next\",\n    \"start\": \"next start\",\n    \"build\": \"next build\"\n  }\n}\n"
  },
  {
    "path": "examples/server-render/pages/[pokemon].js",
    "content": "import Link from 'next/link'\nimport fetcher from '../libs/fetcher'\n\nimport useSWR from 'swr'\n\nconst getURL = pokemon => `https://pokeapi.co/api/v2/pokemon/${pokemon}`\n\nexport default function Pokemon({ pokemon, fallbackData }) {\n  const { data } = useSWR(getURL(pokemon), fetcher, { fallbackData })\n\n  return (\n    <div>\n      <h1>{pokemon}</h1>\n      {data ? (\n        <div>\n          <figure>\n            <img src={data.sprites.front_default} />\n          </figure>\n          <p>height: {data.height}</p>\n          <p>weight: {data.weight}</p>\n          <ul>\n            {data.types.map(({ type }) => (\n              <li key={type.name}>{type.name}</li>\n            ))}\n          </ul>\n        </div>\n      ) : (\n        'loading...'\n      )}\n      <br />\n      <br />\n      <Link href=\"/\">\n        Back\n      </Link>\n    </div>\n  )\n}\n\nexport async function getServerSideProps({ query }) {\n  const data = await fetcher(getURL(query.pokemon))\n  return { props: { fallbackData: data, pokemon: query.pokemon } }\n}"
  },
  {
    "path": "examples/server-render/pages/index.js",
    "content": "import Link from 'next/link'\nimport fetcher from '../libs/fetcher'\n\nimport useSWR from 'swr'\n\nconst URL = 'https://pokeapi.co/api/v2/pokemon/'\n\nexport default function Home({ fallbackData }) {\n  const { data } = useSWR(URL, fetcher, { fallbackData })\n\n  return (\n    <div style={{ textAlign: 'center' }}>\n      <h1>Trending Projects</h1>\n      <div>\n        {data && data.results\n          ? data.results.map(pokemon => (\n              <p key={pokemon.name}>\n                <Link href=\"/[pokemon]\" as={`/${pokemon.name}`}>\n                  {pokemon.name}\n                </Link>\n              </p>\n            ))\n          : 'loading...'}\n      </div>\n    </div>\n  )\n}\n\nexport async function getServerSideProps() {\n  const data = await fetcher(URL)\n  return { props: { fallbackData: data } }\n}"
  },
  {
    "path": "examples/storage-tab-sync/README.md",
    "content": "# Storage Tab Sync\n\n## One-Click Deploy\n\nDeploy your own SWR project with Vercel.\n\n[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?s=https://github.com/vercel/swr/tree/main/examples/storage-tab-sync)\n\n## How to Use\n\nDownload the example:\n\n```bash\ncurl https://codeload.github.com/vercel/swr/tar.gz/main | tar -xz --strip=2 swr-main/examples/storage-tab-sync\ncd storage-tab-sync\n```\n\nInstall it and run:\n\n```bash\nyarn\nyarn dev\n# or\nnpm install\nnpm run dev\n```\n\n## The Idea behind the Example\n\nShow how you could use SWR to synchronize localStorage values between tabs.\n"
  },
  {
    "path": "examples/storage-tab-sync/libs/storage.js",
    "content": "export default async function storage(key) {\n  const value = localStorage.getItem(key)\n  if (!value) return undefined\n  return JSON.parse(value)\n}\n"
  },
  {
    "path": "examples/storage-tab-sync/package.json",
    "content": "{\n  \"name\": \"storage-tab-sync\",\n  \"version\": \"1.0.0\",\n  \"main\": \"index.js\",\n  \"license\": \"MIT\",\n  \"dependencies\": {\n    \"next\": \"latest\",\n    \"react\": \"latest\",\n    \"react-dom\": \"latest\",\n    \"swr\": \"latest\"\n  },\n  \"scripts\": {\n    \"dev\": \"next\",\n    \"start\": \"next start\",\n    \"build\": \"next build\"\n  }\n}\n"
  },
  {
    "path": "examples/storage-tab-sync/pages/index.js",
    "content": "import storage from '../libs/storage'\n\nimport useSWR, { mutate } from 'swr'\n\nexport default function Index() {\n  const { data = { name: \"\" } } = useSWR('user-name', storage)\n\n  function handleChange(event) {\n    localStorage.setItem(\n      'user-name',\n      JSON.stringify({ name: event.target.value })\n    )\n    mutate('user-name', { name: event.target.value })\n  }\n\n  return <div style={{ textAlign: 'center' }}>\n    <label htmlFor=\"name\">Name</label>\n    <input id=\"name\" name=\"name\" type=\"text\" value={data.name} onChange={handleChange} />\n  </div>\n}\n"
  },
  {
    "path": "examples/subscription/README.md",
    "content": "# Subscription\n\n## One-Click Deploy\n\nDeploy your own SWR project with Vercel.\n\n[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?s=https://github.com/vercel/swr/tree/main/examples/subscription)\n\n## How to Use\n\nDownload the example:\n\n```bash\ncurl https://codeload.github.com/vercel/swr/tar.gz/main | tar -xz --strip=2 swr-main/examples/subscription\ncd subscription\n```\n\nInstall it and run:\n\n```bash\nyarn\nyarn dev\n# or\nnpm install\nnpm run dev\n```\n\n## The Idea behind the Example\n\nShow how you could use SWR to subscribe async observable data into your app.\n"
  },
  {
    "path": "examples/subscription/package.json",
    "content": "{\n  \"name\": \"subscription\",\n  \"private\": true,\n  \"license\": \"MIT\",\n  \"dependencies\": {\n    \"next\": \"latest\",\n    \"react\": \"latest\",\n    \"react-dom\": \"latest\",\n    \"swr\": \"latest\"\n  },\n  \"scripts\": {\n    \"dev\": \"next\",\n    \"start\": \"next start\",\n    \"build\": \"next build\"\n  }\n}\n"
  },
  {
    "path": "examples/subscription/pages/index.js",
    "content": "import React from \"react\"\nimport useSWRSubscription from \"swr/subscription\"\nimport EventEmitter from \"events\"\n\nconst event = new EventEmitter()\n\n// Simulating an external data source.\nlet stopped = false\nasync function start () {\n  for (let i = 0; i < 100; i++) {\n    await new Promise(res => setTimeout(res, 1000))\n    if (stopped) return\n    if (i % 3 === 0 && i !== 0) {\n      event.emit(\"error\", new Error(\"error: \" + i));\n    } else {\n      event.emit(\"data\", \"state: \" + i);\n    }\n  }\n}\n\nexport default function page() {\n  const { data, error } = useSWRSubscription('my-sub', (key, { next }) => {\n    event.on(\"data\", (value) => next(undefined, value));\n    event.on(\"error\", (err) => next(err));\n    start();\n    return () => {\n      stopped = true;\n    };\n  })\n\n  return (\n    <div>\n      <h3>SWR Subscription</h3>\n      <h4>Received every second, error when data is times of 3</h4>\n      <div>{data}</div>\n      <div>{error ? error.message : \"\"}</div>\n    </div>\n  )\n}\n"
  },
  {
    "path": "examples/suspense/README.md",
    "content": "# Basic\n\n## One-Click Deploy\n\nDeploy your own SWR project with Vercel.\n\n[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?s=https://github.com/vercel/swr/tree/main/examples/suspense)\n\n## How to Use\n\nDownload the example:\n\n```bash\ncurl https://codeload.github.com/vercel/swr/tar.gz/main | tar -xz --strip=2 swr-main/examples/suspense\ncd suspense\n```\n\nInstall it and run:\n\n```bash\nyarn\nyarn dev\n# or\nnpm install\nnpm run dev\n```\n\n## The Idea behind the Example\n\nShow how to use the SWR suspense option with React suspense.\n"
  },
  {
    "path": "examples/suspense/app/layout.jsx",
    "content": "export default function RootLayout({\n  children\n}) {\n  return (\n    <html lang=\"en\">\n      <body style={{ textAlign: 'center' }}>{children}</body>\n    </html>\n  )\n}\n"
  },
  {
    "path": "examples/suspense/app/rsc/[user]/[repo]/error.jsx",
    "content": "'use client'\nexport default function ErrorPage() {\n  return <div>Error happen</div>;\n}"
  },
  {
    "path": "examples/suspense/app/rsc/[user]/[repo]/loading.jsx",
    "content": "export default function Loading() {\n  return <div>Loading...</div>;\n}"
  },
  {
    "path": "examples/suspense/app/rsc/[user]/[repo]/page.jsx",
    "content": "import Repo from './repo'\nimport fetcher from '../../../../libs/fetch'\nimport Link from 'next/link'\nimport { Suspense } from 'react'\nconst Page = ({ params }) => {\n  const { user, repo } = params\n  const id = `${user}/${repo}`\n  const serverData = fetcher('http://localhost:3000/api/data?id=' + id)\n  return (\n    <div>\n      <div>Repo: {id}</div>\n      <Suspense fallback={<div>Loading stats</div>}>\n        <Repo serverData={serverData} id={id} />\n      </Suspense>\n      <br />\n      <br />\n      <Link href=\"/rsc\">Back</Link>\n    </div>\n  )\n}\n\n\nexport default Page"
  },
  {
    "path": "examples/suspense/app/rsc/[user]/[repo]/repo.jsx",
    "content": "'use client'\nimport fetcher from '../../../../libs/fetch'\nimport useSWR from 'swr'\n\nconst Repo = ({ id, serverData }) => {\n  const { data } = useSWR('/api/data?id=' + id, fetcher, { suspense: true, fallbackData: serverData })\n  return (\n    <>\n      {data ? (\n        <div>\n          <p>forks: {data.forks_count}</p>\n          <p>stars: {data.stargazers_count}</p>\n          <p>watchers: {data.watchers}</p>\n        </div>\n      ) : null}\n\n    </>\n  )\n}\n\nexport default Repo"
  },
  {
    "path": "examples/suspense/app/rsc/loading.jsx",
    "content": "export default function Loading() {\n  return <div>Loading...</div>;\n}"
  },
  {
    "path": "examples/suspense/app/rsc/page.jsx",
    "content": "import fetcher from '../../libs/fetch'\nimport Repos from './repos'\nconst Page = () => {\n  const serverData = fetcher('http://localhost:3000/api/data')\n  return <Repos serverData={serverData} />\n}\n\nexport default Page\n"
  },
  {
    "path": "examples/suspense/app/rsc/repos.jsx",
    "content": "'use client'\nimport useSWR from 'swr'\nimport fetcher from '../../libs/fetch'\nimport Link from 'next/link'\n\nconst Repos = ({ serverData }) => {\n  const { data } = useSWR('/api/data', fetcher, {\n    suspense: true,\n    fallbackData: serverData\n  })\n  return (\n    <>\n      {data.map(project => (\n        <p key={project}>\n          <Link href={`/rsc/${project}`}>\n            {project}\n          </Link>\n        </p>\n      ))}\n    </>\n  )\n}\n\nexport default Repos"
  },
  {
    "path": "examples/suspense/components/error-handling.js",
    "content": "import React from 'react'\n\nexport default class ErrorBoundary extends React.Component {\n  state = { hasError: false, error: null }\n  static getDerivedStateFromError(error) {\n    return {\n      hasError: true,\n      error\n    }\n  }\n  render() {\n    if (this.state.hasError) {\n      return this.props.fallback\n    }\n    return this.props.children\n  }\n}\n"
  },
  {
    "path": "examples/suspense/libs/fetch.js",
    "content": "export default async function fetcher(...args) {\n  const res = await fetch(...args)\n  return res.json()\n}\n"
  },
  {
    "path": "examples/suspense/next-env.d.ts",
    "content": "/// <reference types=\"next\" />\n/// <reference types=\"next/image-types/global\" />\n/// <reference types=\"next/navigation-types/compat/navigation\" />\n\n// NOTE: This file should not be edited\n// see https://nextjs.org/docs/basic-features/typescript for more information.\n"
  },
  {
    "path": "examples/suspense/package.json",
    "content": "{\n  \"name\": \"suspense\",\n  \"version\": \"1.0.0\",\n  \"main\": \"index.js\",\n  \"license\": \"MIT\",\n  \"dependencies\": {\n    \"next\": \"latest\",\n    \"react\": \"latest\",\n    \"react-dom\": \"latest\",\n    \"swr\": \"latest\"\n  },\n  \"scripts\": {\n    \"dev\": \"next\",\n    \"start\": \"next start\",\n    \"build\": \"next build\"\n  }\n}\n"
  },
  {
    "path": "examples/suspense/pages/[user]/[repo].js",
    "content": "import { Suspense } from 'react'\nimport Link from 'next/link'\nimport fetcher from '../../libs/fetch'\nimport ErrorHandling from '../../components/error-handling'\nimport useSWR from 'swr'\n\nconst Detail = ({ id, serverData }) => {\n  const { data } = useSWR('/api/data?id=' + id, fetcher, { suspense: true, fallbackData: serverData })\n\n  return (\n    <>\n      {data ? (\n        <div>\n          <p>forks: {data.forks_count}</p>\n          <p>stars: {data.stargazers_count}</p>\n          <p>watchers: {data.watchers}</p>\n        </div>\n      ) : null}\n    </>\n  )\n}\n\nexport default function Repo({ id, serverData }) {\n  return (\n    <div style={{ textAlign: 'center' }}>\n      <h1>{id}</h1>\n      <Suspense fallback={<div>loading...</div>}>\n        <ErrorHandling fallback={<div>oooops!</div>}>\n          <Detail id={id} serverData={serverData}></Detail>\n        </ErrorHandling>\n      </Suspense>\n      <br />\n      <br />\n      <Link href=\"/\">Back</Link>\n    </div>\n  )\n}\n\nexport const getServerSideProps = async ({ params }) => {\n  const { user, repo } = params\n  const id = `${user}/${repo}`\n  const data = await fetcher('http://localhost:3000/api/data?id=' + id).catch(() => {})\n  return { props: { serverData: data, id } }\n}"
  },
  {
    "path": "examples/suspense/pages/api/data.js",
    "content": "const projects = [\n  'facebook/flipper',\n  'vuejs/vuepress',\n  'rust-lang/rust',\n  'vercel/next.js',\n  'emperor/clothes'\n]\n\nexport default function api(req, res) {\n  if (req.query.id) {\n    if (req.query.id === projects[4]) {\n      setTimeout(() => {\n        res.json({ msg: 'not found' })\n      })\n    } else {\n      // a slow endpoint for getting repo data\n      fetch(`https://api.github.com/repos/${req.query.id}`)\n        .then(res => res.json())\n        .then(data => {\n          setTimeout(() => {\n            res.json(data)\n          }, 2000)\n        })\n    }\n  } else {\n    setTimeout(() => {\n      res.json(projects)\n    }, 2000)\n  }\n}\n"
  },
  {
    "path": "examples/suspense/pages/index.js",
    "content": "import { Suspense } from 'react'\nimport Link from 'next/link'\nimport fetcher from '../libs/fetch'\n\nimport useSWR from 'swr'\n\nconst Repos = ({ serverData }) => {\n  const { data } = useSWR('/api/data', fetcher, {\n    suspense: true,\n    fallbackData: serverData\n  })\n\n  return (\n    <>\n      {data.map(project => (\n        <p key={project}>\n          <Link href=\"/[user]/[repo]\" as={`/${project}`}>\n            {project}\n          </Link>\n        </p>\n      ))}\n    </>\n  )\n}\n\nexport default function Index({ serverData }) {\n  return (\n    <div style={{ textAlign: 'center' }}>\n      <h1>Trending Projects</h1>\n      <Suspense fallback={<div>loading...</div>}>\n        <Repos serverData={serverData}></Repos>\n      </Suspense>\n    </div>\n  )\n}\n\nexport const getServerSideProps = async () => {\n  const data = await fetcher('http://localhost:3000/api/data')\n  return { props: { serverData: data } }\n}\n"
  },
  {
    "path": "examples/suspense-global/README.md",
    "content": "# Basic\n\n## One-Click Deploy\n\nDeploy your own SWR project with Vercel.\n\n[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?s=https://github.com/vercel/swr/tree/main/examples/suspense)\n\n## How to Use\n\nDownload the example:\n\n```bash\ncurl https://codeload.github.com/vercel/swr/tar.gz/main | tar -xz --strip=2 swr-main/examples/suspense\ncd suspense\n```\n\nInstall it and run:\n\n```bash\nyarn\nyarn dev\n# or\nnpm install\nnpm run dev\n```\n\n## The Idea behind the Example\n\nShow how to use the SWR suspense option with React suspense.\n"
  },
  {
    "path": "examples/suspense-global/components/error-handling.ts",
    "content": "import React from 'react'\n\nexport default class ErrorBoundary extends React.Component<any> {\n  state = { hasError: false, error: null }\n  static getDerivedStateFromError(error: any) {\n    return {\n      hasError: true,\n      error\n    }\n  }\n  render() {\n    if (this.state.hasError) {\n      return this.props.fallback\n    }\n    return this.props.children\n  }\n}\n"
  },
  {
    "path": "examples/suspense-global/global-swr-config.tsx",
    "content": "'use client'\n\nimport { SWRConfig } from 'swr'\n\nimport fetcher from './libs/fetch'\n\ndeclare module 'swr' {\n  interface SWRGlobalConfig {\n    suspense: true\n  }\n}\n\nexport function GlobalSWRConfig({ children }: { children: React.ReactNode }) {\n  return (\n    <SWRConfig\n      value={{\n        fetcher,\n        suspense: true\n      }}\n    >\n      {children}\n    </SWRConfig>\n  )\n}\n"
  },
  {
    "path": "examples/suspense-global/libs/fetch.ts",
    "content": "export default async function fetcher(...args: [any]) {\n  const res = await fetch(...args)\n  if (!res.ok) {\n    throw new Error('An error occurred while fetching the data.')\n  } else {\n    return res.json()\n  }\n}\n"
  },
  {
    "path": "examples/suspense-global/next-env.d.ts",
    "content": "/// <reference types=\"next\" />\n/// <reference types=\"next/image-types/global\" />\n\n// NOTE: This file should not be edited\n// see https://nextjs.org/docs/pages/building-your-application/configuring/typescript for more information.\n"
  },
  {
    "path": "examples/suspense-global/package.json",
    "content": "{\n  \"name\": \"suspense-global\",\n  \"version\": \"1.0.0\",\n  \"main\": \"index.js\",\n  \"license\": \"MIT\",\n  \"dependencies\": {\n    \"next\": \"latest\",\n    \"react\": \"latest\",\n    \"react-dom\": \"latest\",\n    \"swr\": \"latest\"\n  },\n  \"scripts\": {\n    \"dev\": \"next\",\n    \"start\": \"next start\",\n    \"build\": \"next build\"\n  }\n}\n"
  },
  {
    "path": "examples/suspense-global/pages/[user]/[repo].tsx",
    "content": "import dynamic from 'next/dynamic'\nimport Link from 'next/link'\nimport { Suspense } from 'react'\nimport ErrorHandling from '../../components/error-handling'\nimport { useRouter } from 'next/router'\n\nconst Detail = dynamic(() => import('./detail'), {\n  ssr: false\n})\n\nexport default function Repo() {\n  const router = useRouter()\n  if (!router.isReady) return null\n  const { user, repo } = router.query\n  const id = `${user}/${repo}`\n\n  return (\n    <div style={{ textAlign: 'center' }}>\n      <h1>{id}</h1>\n      <Suspense fallback={<div>loading...</div>}>\n        <ErrorHandling fallback={<div>oooops!</div>}>\n          <Detail id={id}></Detail>\n        </ErrorHandling>\n      </Suspense>\n      <br />\n      <br />\n      <Link href=\"/\">Back</Link>\n    </div>\n  )\n}\n"
  },
  {
    "path": "examples/suspense-global/pages/[user]/detail.tsx",
    "content": "import useSWR from 'swr'\nimport { RepoData } from '../api/data'\n\nconst Detail = ({ id }: { id: string }) => {\n  const { data } = useSWR<RepoData>('/api/data?id=' + id)\n\n  return (\n    <>\n      {data ? (\n        <div>\n          <p>forks: {data.forks_count}</p>\n          <p>stars: {data.stargazers_count}</p>\n          <p>watchers: {data.watchers}</p>\n        </div>\n      ) : null}\n    </>\n  )\n}\n\nexport default Detail\n"
  },
  {
    "path": "examples/suspense-global/pages/_app.tsx",
    "content": "import type { AppProps } from 'next/app'\nimport { GlobalSWRConfig } from 'global-swr-config'\n\nexport default function MyApp({ Component, pageProps }: AppProps) {\n  return (\n    <GlobalSWRConfig>\n      <Component {...pageProps} />\n    </GlobalSWRConfig>\n  )\n}\n"
  },
  {
    "path": "examples/suspense-global/pages/api/data.ts",
    "content": "import { NextApiRequest, NextApiResponse } from 'next'\n\nconst projects = [\n  'facebook/flipper',\n  'vuejs/vuepress',\n  'rust-lang/rust',\n  'vercel/next.js',\n  'emperor/clothes'\n] as const\n\nexport type ProjectsData = typeof projects\n\nexport interface RepoData {\n  forks_count: number\n  stargazers_count: number\n  watchers: number\n}\n\nexport default function api(req: NextApiRequest, res: NextApiResponse) {\n  if (req.query.id) {\n    if (req.query.id === projects[4]) {\n      setTimeout(() => {\n        res.status(404).json({ msg: 'not found' })\n      })\n    } else {\n      // a slow endpoint for getting repo data\n      fetch(`https://api.github.com/repos/${req.query.id}`)\n        .then(res => res.json())\n        .then(data => {\n          setTimeout(() => {\n            res.json(data)\n          }, 2000)\n        })\n    }\n  } else {\n    setTimeout(() => {\n      res.json(projects)\n    }, 2000)\n  }\n}\n"
  },
  {
    "path": "examples/suspense-global/pages/index.tsx",
    "content": "import { Suspense } from 'react'\nimport dynamic from 'next/dynamic'\n\nconst Repos = dynamic(() => import('./repos'), {\n  ssr: false\n})\n\nexport default function Index() {\n  return (\n    <div style={{ textAlign: 'center' }}>\n      <h1>Trending Projects</h1>\n      <Suspense fallback={<div>loading...</div>}>\n        <Repos />\n      </Suspense>\n    </div>\n  )\n}\n"
  },
  {
    "path": "examples/suspense-global/pages/repos.tsx",
    "content": "import Link from 'next/link'\nimport { ProjectsData } from './api/data'\n\nimport useSWR from 'swr'\n\nconst Repos = () => {\n  const { data } = useSWR<ProjectsData>('/api/data')\n\n  return (\n    <>\n      {data.map(project => (\n        <p key={project}>\n          <Link href=\"/[user]/[repo]\" as={`/${project}`}>\n            {project}\n          </Link>\n        </p>\n      ))}\n    </>\n  )\n}\n\nexport default Repos\n"
  },
  {
    "path": "examples/suspense-global/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"es5\",\n    \"lib\": [\"dom\", \"dom.iterable\", \"esnext\"],\n    \"allowJs\": true,\n    \"skipLibCheck\": true,\n    \"strict\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"noEmit\": true,\n    \"esModuleInterop\": true,\n    \"module\": \"esnext\",\n    \"moduleResolution\": \"node\",\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"jsx\": \"preserve\",\n    \"incremental\": true,\n    \"noImplicitAny\": true,\n    \"plugins\": [\n      {\n        \"name\": \"next\"\n      }\n    ],\n    \"baseUrl\": \".\",\n    \"paths\": {\n      \"~/*\": [\"./*\"]\n    }\n  },\n  \"include\": [\"next-env.d.ts\", \"**/*.ts\", \"**/*.tsx\", \".next/types/**/*.ts\"],\n  \"exclude\": [\"node_modules\"]\n}\n"
  },
  {
    "path": "examples/suspense-retry/app/api/route.ts",
    "content": "import { NextResponse } from 'next/server'\n\nexport const GET = () => {\n  return Math.random() < 0.5\n    ? NextResponse.json({\n        data: 'success'\n      })\n    : new Response('Bad', {\n        status: 500\n      })\n}\n"
  },
  {
    "path": "examples/suspense-retry/app/layout.tsx",
    "content": "export default function RootLayout({\n  children\n}: {\n  children: React.ReactNode\n}) {\n  return (\n    <html lang=\"en\">\n      <body>{children}</body>\n    </html>\n  )\n}\n"
  },
  {
    "path": "examples/suspense-retry/app/manual-retry.tsx",
    "content": "'use client'\nimport { Suspense } from 'react'\nimport { ErrorBoundary } from 'react-error-boundary'\nimport { useRemoteData, preloadRemote } from './use-remote-data'\n\nconst Demo = () => {\n  const { data } = useRemoteData()\n  return <div>{data}</div>\n}\npreloadRemote()\n\nfunction Fallback({ resetErrorBoundary }: any) {\n  return (\n    <div role=\"alert\">\n      <p>Something went wrong:</p>\n      <button\n        onClick={() => {\n          resetErrorBoundary()\n        }}\n      >\n        retry\n      </button>\n    </div>\n  )\n}\n\nfunction RemoteData() {\n  return (\n    <div className=\"App\">\n      <ErrorBoundary\n        FallbackComponent={Fallback}\n        onReset={() => {\n          preloadRemote()\n        }}\n      >\n        <Suspense fallback={<div>loading</div>}>\n          <Demo />\n        </Suspense>\n      </ErrorBoundary>\n    </div>\n  )\n}\n\nexport default RemoteData\n"
  },
  {
    "path": "examples/suspense-retry/app/page.tsx",
    "content": "import { Suspense } from 'react'\nimport dynamic from 'next/dynamic'\n\nconst RemoteData = dynamic(() => import('./manual-retry'), {\n  ssr: false\n})\n\nexport default function HomePage() {\n  return (\n    <Suspense fallback={<div>loading component</div>}>\n      <RemoteData></RemoteData>\n    </Suspense>\n  )\n}\n"
  },
  {
    "path": "examples/suspense-retry/app/use-remote-data.ts",
    "content": "'use client'\nimport useSWR from 'swr'\nimport { preload } from 'swr'\n\nlet count = 0\nconst fetcher = () => {\n  count++\n  if (count === 1) return Promise.reject('wrong')\n  return fetch('/api')\n    .then(r => r.json())\n    .then(r => r.data)\n}\n\nconst key = 'manual-retry'\n\nexport const useRemoteData = () =>\n  useSWR(key, fetcher, {\n    suspense: true\n  })\n\nexport const preloadRemote = () => preload(key, fetcher)\n"
  },
  {
    "path": "examples/suspense-retry/next-env.d.ts",
    "content": "/// <reference types=\"next\" />\n/// <reference types=\"next/image-types/global\" />\n/// <reference types=\"next/navigation-types/compat/navigation\" />\n\n// NOTE: This file should not be edited\n// see https://nextjs.org/docs/basic-features/typescript for more information.\n"
  },
  {
    "path": "examples/suspense-retry/next.config.js",
    "content": "/** @type {import('next').NextConfig} */\nconst nextConfig = {\n  experimental: {\n    serverActions: true,\n  },\n}\n\nmodule.exports = nextConfig\n"
  },
  {
    "path": "examples/suspense-retry/package.json",
    "content": "{\n  \"name\": \"site\",\n  \"version\": \"0.1.0\",\n  \"private\": true,\n  \"scripts\": {\n    \"dev\": \"next dev\",\n    \"build\": \"next build\",\n    \"start\": \"next start\",\n    \"lint\": \"next lint\"\n  },\n  \"dependencies\": {\n    \"@types/node\": \"^20.2.5\",\n    \"@types/react\": \"^18.2.8\",\n    \"@types/react-dom\": \"18.2.4\",\n    \"next\": \"^latest\",\n    \"react\": \"^18.2.0\",\n    \"react-dom\": \"^18.2.0\",\n    \"typescript\": \"5.1.3\",\n    \"swr\": \"*\"\n  }\n}\n"
  },
  {
    "path": "examples/suspense-retry/pages/retry.tsx",
    "content": "import { Suspense } from 'react'\nimport dynamic from 'next/dynamic'\n\nconst RemoteData = dynamic(() => import('../app/manual-retry'), {\n  ssr: false\n})\n\nexport default function HomePage() {\n  return (\n    <Suspense fallback={<div>loading component</div>}>\n      <RemoteData></RemoteData>\n    </Suspense>\n  )\n}\n"
  },
  {
    "path": "examples/suspense-retry/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"es5\",\n    \"lib\": [\"dom\", \"dom.iterable\", \"esnext\"],\n    \"allowJs\": true,\n    \"skipLibCheck\": true,\n    \"strict\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"noEmit\": true,\n    \"esModuleInterop\": true,\n    \"module\": \"esnext\",\n    \"moduleResolution\": \"node\",\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"jsx\": \"preserve\",\n    \"incremental\": true,\n    \"plugins\": [\n      {\n        \"name\": \"next\"\n      }\n    ],\n    \"baseUrl\": \".\",\n    \"paths\": {\n      \"~/*\": [\"./*\"]\n    }\n  },\n  \"include\": [\"next-env.d.ts\", \"**/*.ts\", \"**/*.tsx\", \".next/types/**/*.ts\"],\n  \"exclude\": [\"node_modules\"]\n}\n"
  },
  {
    "path": "immutable/package.json",
    "content": "{\n  \"main\": \"../dist/immutable/index.js\",\n  \"module\": \"../dist/immutable/index.mjs\",\n  \"types\": \"../dist/immutable/index.d.ts\",\n  \"private\": true\n}\n"
  },
  {
    "path": "infinite/package.json",
    "content": "{\n  \"main\": \"../dist/infinite/index.js\",\n  \"module\": \"../dist/infinite/index.mjs\",\n  \"types\": \"../dist/infinite/index.d.ts\",\n  \"private\": true\n}\n"
  },
  {
    "path": "jest.config.build.js",
    "content": "import config from './jest.config.js'\n\nconst useBuildConfig = {\n  ...config,\n  // override to use build files\n  moduleNameMapper: {}\n}\n\nexport default useBuildConfig\n"
  },
  {
    "path": "jest.config.js",
    "content": "const config = {\n  testEnvironment: 'jsdom',\n  testRegex: '/test/.*\\\\.test\\\\.tsx?$',\n  testPathIgnorePatterns: ['/node_modules/', '/e2e/'],\n  modulePathIgnorePatterns: ['<rootDir>/examples/'],\n  setupFilesAfterEnv: ['<rootDir>/test/jest-setup.ts'],\n  moduleNameMapper: {\n    '^swr$': '<rootDir>/src/index/index.ts',\n    '^swr/infinite$': '<rootDir>/src/infinite/index.ts',\n    '^swr/immutable$': '<rootDir>/src/immutable/index.ts',\n    '^swr/subscription$': '<rootDir>/src/subscription/index.ts',\n    '^swr/mutation$': '<rootDir>/src/mutation/index.ts',\n    '^swr/_internal$': '<rootDir>/src/_internal/index.ts'\n  },\n  transform: {\n    '^.+\\\\.(t|j)sx?$': '@swc/jest'\n  },\n  coveragePathIgnorePatterns: [\n    '/node_modules/', \n    '/dist/', \n    '/test/',\n    '<rootDir>/src/_internal/utils/env.ts'\n  ],\n  coverageReporters: ['text', 'html'],\n  reporters: [['github-actions', { silent: false }], 'summary']\n}\n\n\nexport default config"
  },
  {
    "path": "mutation/package.json",
    "content": "{\n  \"main\": \"../dist/mutation/index.js\",\n  \"module\": \"../dist/mutation/index.mjs\",\n  \"types\": \"../dist/mutation/index.d.ts\",\n  \"private\": true\n}\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"swr\",\n  \"version\": \"2.4.1\",\n  \"description\": \"React Hooks library for remote data fetching\",\n  \"keywords\": [\n    \"swr\",\n    \"react\",\n    \"hooks\",\n    \"request\",\n    \"cache\",\n    \"fetch\"\n  ],\n  \"packageManager\": \"pnpm@10.24.0\",\n  \"main\": \"./dist/index/index.js\",\n  \"module\": \"./dist/index/index.mjs\",\n  \"types\": \"./dist/index/index.d.ts\",\n  \"sideEffects\": false,\n  \"exports\": {\n    \"./package.json\": \"./package.json\",\n    \".\": {\n      \"react-server\": \"./dist/index/react-server.mjs\",\n      \"import\": {\n        \"types\": \"./dist/index/index.d.mts\",\n        \"default\": \"./dist/index/index.mjs\"\n      },\n      \"require\": {\n        \"types\": \"./dist/index/index.d.ts\",\n        \"default\": \"./dist/index/index.js\"\n      }\n    },\n    \"./infinite\": {\n      \"react-server\": \"./dist/infinite/react-server.mjs\",\n      \"import\": {\n        \"types\": \"./dist/infinite/index.d.mts\",\n        \"default\": \"./dist/infinite/index.mjs\"\n      },\n      \"require\": {\n        \"types\": \"./dist/infinite/index.d.ts\",\n        \"default\": \"./dist/infinite/index.js\"\n      }\n    },\n    \"./immutable\": {\n      \"import\": {\n        \"types\": \"./dist/immutable/index.d.mts\",\n        \"default\": \"./dist/immutable/index.mjs\"\n      },\n      \"require\": {\n        \"types\": \"./dist/immutable/index.d.ts\",\n        \"default\": \"./dist/immutable/index.js\"\n      }\n    },\n    \"./subscription\": {\n      \"import\": {\n        \"types\": \"./dist/subscription/index.d.mts\",\n        \"default\": \"./dist/subscription/index.mjs\"\n      },\n      \"require\": {\n        \"types\": \"./dist/subscription/index.d.ts\",\n        \"default\": \"./dist/subscription/index.js\"\n      }\n    },\n    \"./mutation\": {\n      \"import\": {\n        \"types\": \"./dist/mutation/index.d.mts\",\n        \"default\": \"./dist/mutation/index.mjs\"\n      },\n      \"require\": {\n        \"types\": \"./dist/mutation/index.d.ts\",\n        \"default\": \"./dist/mutation/index.js\"\n      }\n    },\n    \"./_internal\": {\n      \"react-server\": \"./dist/_internal/react-server.mjs\",\n      \"import\": {\n        \"types\": \"./dist/_internal/index.d.mts\",\n        \"default\": \"./dist/_internal/index.mjs\"\n      },\n      \"require\": {\n        \"types\": \"./dist/_internal/index.d.ts\",\n        \"default\": \"./dist/_internal/index.js\"\n      }\n    }\n  },\n  \"files\": [\n    \"dist\",\n    \"infinite\",\n    \"immutable\",\n    \"subscription\",\n    \"mutation\",\n    \"_internal\"\n  ],\n  \"repository\": \"github:vercel/swr\",\n  \"homepage\": \"https://swr.vercel.app\",\n  \"license\": \"MIT\",\n  \"scripts\": {\n    \"prepare\": \"husky install\",\n    \"csb:install\": \"corepack enable && corepack pnpm i\",\n    \"csb:build\": \"pnpm build\",\n    \"clean\": \"rimraf ./dist && rimraf playwright-report test-result\",\n    \"watch\": \"bunchee -w\",\n    \"build\": \"bunchee\",\n    \"build:e2e\": \"pnpm next build e2e/site\",\n    \"attw\": \"attw --pack .\",\n    \"types:check\": \"tsc --noEmit\",\n    \"prepublishOnly\": \"pnpm clean && pnpm build\",\n    \"publish-beta\": \"pnpm publish --tag beta\",\n    \"format\": \"prettier --write ./**/*.{ts,tsx}\",\n    \"lint\": \"eslint . --ext .ts,.tsx --cache\",\n    \"lint:fix\": \"pnpm lint --fix\",\n    \"coverage\": \"jest --coverage\",\n    \"test-typing\": \"tsc -p test/tsconfig.json && tsc -p test/type/tsconfig.json && tsc -p test/type/suspense/tsconfig.json\",\n    \"test\": \"jest\",\n    \"test:build\": \"jest --config jest.config.build.js\",\n    \"test:e2e\": \"playwright test\",\n    \"run-all-checks\": \"pnpm types:check && pnpm lint && pnpm test-typing\"\n  },\n  \"lint-staged\": {\n    \"*.{ts,tsx}\": [\n      \"eslint --fix --cache\",\n      \"prettier --write\"\n    ]\n  },\n  \"devDependencies\": {\n    \"@arethetypeswrong/cli\": \"^0.18.2\",\n    \"@edge-runtime/jest-environment\": \"^4.0.0\",\n    \"@eslint/compat\": \"^2.0.0\",\n    \"@eslint/eslintrc\": \"^3.3.1\",\n    \"@eslint/js\": \"^9.39.1\",\n    \"@playwright/test\": \"1.57.0\",\n    \"@swc/core\": \"^1.15.3\",\n    \"@swc/jest\": \"0.2.39\",\n    \"@testing-library/dom\": \"^10.4.1\",\n    \"@testing-library/jest-dom\": \"^6.9.1\",\n    \"@testing-library/react\": \"^16.3.0\",\n    \"@type-challenges/utils\": \"0.1.1\",\n    \"@types/jest\": \"^30.0.0\",\n    \"@types/node\": \"^22.19.1\",\n    \"@types/react\": \"^19.2.7\",\n    \"@types/use-sync-external-store\": \"^1.5.0\",\n    \"@typescript-eslint/eslint-plugin\": \"8.48.0\",\n    \"@typescript-eslint/parser\": \"8.48.0\",\n    \"bunchee\": \"^6.6.2\",\n    \"eslint\": \"9.39.1\",\n    \"eslint-config-prettier\": \"10.1.8\",\n    \"eslint-plugin-jest-dom\": \"5.5.0\",\n    \"eslint-plugin-react\": \"7.37.5\",\n    \"eslint-plugin-react-hooks\": \"7.0.1\",\n    \"eslint-plugin-testing-library\": \"7.13.5\",\n    \"globals\": \"^16.5.0\",\n    \"husky\": \"9.1.7\",\n    \"jest\": \"30.2.0\",\n    \"jest-environment-jsdom\": \"29.7.0\",\n    \"lint-staged\": \"16.2.7\",\n    \"next\": \"16.1.1\",\n    \"prettier\": \"2.8.8\",\n    \"react\": \"^18.2.0\",\n    \"react-dom\": \"^18.2.0\",\n    \"react-error-boundary\": \"^5.0.0\",\n    \"rimraf\": \"6.1.2\",\n    \"semver\": \"^7.5.1\",\n    \"swr\": \"workspace:*\",\n    \"typescript\": \"5.9.3\",\n    \"typescript-eslint\": \"^8.48.0\"\n  },\n  \"peerDependencies\": {\n    \"react\": \"^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0\"\n  },\n  \"prettier\": {\n    \"tabWidth\": 2,\n    \"semi\": false,\n    \"useTabs\": false,\n    \"singleQuote\": true,\n    \"arrowParens\": \"avoid\",\n    \"trailingComma\": \"none\"\n  },\n  \"dependencies\": {\n    \"dequal\": \"^2.0.3\",\n    \"use-sync-external-store\": \"^1.6.0\"\n  }\n}\n"
  },
  {
    "path": "playwright.config.js",
    "content": "import { defineConfig, devices } from '@playwright/test'\n\nexport default defineConfig({\n  webServer: {\n    command: 'pnpm next start e2e/site --port 4000',\n    reuseExistingServer: !process.env.CI,\n    port: 4000\n  },\n  testDir: './e2e',\n  /* The base directory, relative to the config file, for snapshot files created with toMatchSnapshot and toHaveScreenshot. */\n  snapshotDir: './e2e/__snapshots__',\n  /* Maximum time one test can run for. */\n  timeout: 10 * 1000,\n  /* Run tests in files in parallel */\n  fullyParallel: true,\n  /* Fail the build on CI if you accidentally left test.only in the source code. */\n  forbidOnly: !!process.env.CI,\n  /* Retry on CI only */\n  retries: process.env.CI ? 2 : 0,\n  /* Opt out of parallel tests on CI. */\n  workers: process.env.CI ? 1 : undefined,\n  /* Reporter to use. See https://playwright.dev/docs/test-reporters */\n  reporter: process.env.CI\n    ? [['github'], ['html', { open: 'on-failure' }]]\n    : [['html', { open: 'on-failure' }]],\n  /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */\n  use: {\n    baseURL: 'http://localhost:4000',\n    /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */\n    trace: process.env.CI ? 'on-first-retry' : 'on',\n    ...devices['Desktop Chrome']\n  }\n})\n"
  },
  {
    "path": "pnpm-workspace.yaml",
    "content": "packages:\n  - '_internal'\n  - 'core'\n  - 'immutable'\n  - 'infinite'\n  - 'mutation'"
  },
  {
    "path": "scripts/bump-next-version.js",
    "content": "const fs = require('fs')\nconst path = require('path')\nconst { execSync } = require('child_process')\nconst semver = require('semver')\n\nconst packageJsonPath = path.join(__dirname, '../package.json')\nconst packageJsonData = fs.readFileSync(packageJsonPath, 'utf8')\nconst packageJson = JSON.parse(packageJsonData)\n\nlet version = packageJson.version\nconst releaseType = process.env.RELEASE_TYPE || 'beta'\nconst semverType = process.env.SEMVER_TYPE\n\nfunction bumpVersion(version) {\n  if (process.env.DRY_RUN) {\n    console.log(`npm version ${version}`)\n  } else {\n    try {\n      execSync(`npm version ${version}`, { stdio: 'inherit' })\n    } catch (error) {\n      console.error('Failed to execute npm version:', error)\n      process.exit(1)\n    }\n  }\n}\n\nif (releaseType === 'beta') {\n  if (semver.prerelease(version)) {\n    version = semver.inc(version, 'prerelease')\n  } else {\n    version = semver.inc(version, 'pre' + semverType, 'beta')\n  }\n} else if (releaseType === 'stable') {\n  if (!semverType) {\n    console.error('Missing semver type. Expected \"patch\", \"minor\" or \"major\".')\n    process.exit(1)\n  }\n  version = semver.inc(version, semverType)\n} else {\n  console.error('Invalid release type. Expected \"beta\" or \"stable\".')\n  process.exit(1)\n}\n\nbumpVersion(version)\n"
  },
  {
    "path": "src/_internal/constants.ts",
    "content": "export const INFINITE_PREFIX = '$inf$'\n"
  },
  {
    "path": "src/_internal/events.ts",
    "content": "export const FOCUS_EVENT = 0\nexport const RECONNECT_EVENT = 1\nexport const MUTATE_EVENT = 2\nexport const ERROR_REVALIDATE_EVENT = 3\n"
  },
  {
    "path": "src/_internal/index.react-server.ts",
    "content": "export { serialize } from './utils/serialize'\nexport { SWRConfig } from './index'\nexport { INFINITE_PREFIX } from './constants'\n"
  },
  {
    "path": "src/_internal/index.ts",
    "content": "import SWRConfig from './utils/config-context'\nimport * as revalidateEvents from './events'\nimport { INFINITE_PREFIX } from './constants'\n\nexport { SWRConfig, revalidateEvents, INFINITE_PREFIX }\n\nexport { initCache } from './utils/cache'\nexport { defaultConfig, cache, mutate, compare } from './utils/config'\nimport { setupDevTools } from './utils/devtools'\nexport * from './utils/env'\nexport { SWRGlobalState } from './utils/global-state'\nexport { stableHash } from './utils/hash'\nexport * from './utils/helper'\nexport * from './utils/shared'\nexport { mergeConfigs } from './utils/merge-config'\nexport { internalMutate } from './utils/mutate'\nexport { normalize } from './utils/normalize-args'\nexport { withArgs } from './utils/resolve-args'\nexport { serialize } from './utils/serialize'\nexport { subscribeCallback } from './utils/subscribe-key'\nexport { getTimestamp } from './utils/timestamp'\nexport { useSWRConfig } from './utils/use-swr-config'\nexport { preset, defaultConfigOptions } from './utils/web-preset'\nexport { withMiddleware } from './utils/with-middleware'\nexport { preload } from './utils/preload'\n\nexport * from './types'\n\nsetupDevTools()\n"
  },
  {
    "path": "src/_internal/types.ts",
    "content": "import type { SWRGlobalConfig } from '../index'\nimport type * as revalidateEvents from './events'\n\n/**\n * Global state tuple containing SWR's internal state management structures.\n *\n * This is the core state structure that manages all SWR operations internally.\n * Each element serves a specific purpose in the SWR ecosystem.\n *\n * @internal\n */\nexport type GlobalState = [\n  /** Event revalidators: Maps cache keys to arrays of revalidation callbacks */\n  Record<string, RevalidateCallback[]>,\n  /** Mutation timestamps: Maps cache keys to [start_timestamp, end_timestamp] tuples */\n  Record<string, [number, number]>,\n  /** Fetch cache: Maps cache keys to [data, timestamp] tuples */\n  Record<string, [any, number]>,\n  /** Preload cache: Maps cache keys to fetcher responses */\n  Record<string, FetcherResponse<any>>,\n  /** Scoped mutator function for cache updates */\n  ScopedMutator,\n  /** Cache setter function with prev/current value comparison */\n  (key: string, value: any, prev: any) => void,\n  /** Cache subscriber function that returns an unsubscribe function */\n  (key: string, callback: (current: any, prev: any) => void) => () => void\n]\n/**\n * Response type that can be returned by fetcher functions.\n *\n * @template Data - The type of data returned by the fetcher\n * @public\n */\nexport type FetcherResponse<Data = unknown> = Data | Promise<Data>\n\n/**\n * Basic fetcher function that accepts any arguments and returns data or a promise.\n *\n * This is the most permissive fetcher type, allowing any number of arguments\n * of any type. Used when type safety is not required or when dealing with\n * dynamic fetcher signatures.\n *\n * @template Data - The type of data returned by the fetcher\n * @param args - Variable arguments passed to the fetcher\n * @returns Data or a Promise that resolves to data\n * @public\n */\nexport type BareFetcher<Data = unknown> = (\n  ...args: any[]\n) => FetcherResponse<Data>\n\n/**\n * Typed fetcher function that is constrained by the SWR key type.\n *\n * Provides type safety by ensuring the fetcher argument matches the key type.\n * The conditional type logic ensures that:\n * - If the key is a function returning a value, the fetcher receives that value\n * - If the key is falsy (null, undefined, false), the fetcher is never called\n * - Otherwise, the fetcher receives the key directly as its argument\n *\n * @template Data - The type of data returned by the fetcher\n * @template SWRKey - The type of the SWR key, used to infer fetcher arguments\n * @public\n */\nexport type Fetcher<\n  Data = unknown,\n  SWRKey extends Key = Key\n> = SWRKey extends () => infer Arg | null | undefined | false\n  ? (arg: Arg) => FetcherResponse<Data>\n  : SWRKey extends null | undefined | false\n  ? never\n  : SWRKey extends infer Arg\n  ? (arg: Arg) => FetcherResponse<Data>\n  : never\n\n/**\n * Determines if data should block rendering based on suspense configuration.\n *\n * This conditional type is used internally to determine the return type of `data`\n * in SWRResponse. When suspense is enabled or fallbackData is provided, data\n * will never be undefined, allowing for non-nullable return types.\n *\n * The type resolution follows this logic:\n * 1. If global suspense is enabled → `true` (data never undefined)\n * 2. If no options provided → `false` (data can be undefined)\n * 3. If suspense is enabled in options → `true` (data never undefined)\n * 4. If fallbackData is provided → `true` (data never undefined)\n * 5. Otherwise → `false` (data can be undefined)\n *\n * @template Data - The data type\n * @template Options - The SWR configuration options\n * @returns `true` if data is guaranteed to be defined, `false` if it can be undefined\n * @internal\n */\nexport type BlockingData<\n  Data = any,\n  Options = SWRDefaultOptions<Data>\n> = SWRGlobalConfig extends { suspense: true }\n  ? true\n  : Options extends undefined\n  ? false\n  : Options extends { suspense: true }\n  ? true\n  : Options extends { fallbackData: Data | Promise<Data> }\n  ? true\n  : false\n\n/**\n * Configuration types that are only used internally, not exposed to the user.\n *\n * These options are managed internally by SWR and passed between internal\n * functions. They are not part of the public API and should not be used\n * directly by consumers.\n *\n * @internal\n */\nexport interface InternalConfiguration {\n  /** The cache instance used to store SWR data and state */\n  cache: Cache\n  /** Scoped mutator function for updating cache entries */\n  mutate: ScopedMutator\n}\n\n/**\n * Public configuration options for SWR.\n *\n * This interface defines all the configuration options that users can pass\n * to customize SWR's behavior. These options can be provided globally via\n * SWRConfig or per-hook via the config parameter.\n *\n * @template Data - The type of data returned by the fetcher\n * @template Error - The type of error that can be thrown\n * @template Fn - The fetcher function type\n *\n * @public\n * @see {@link https://swr.vercel.app/docs/options | SWR Options Documentation}\n */\nexport interface PublicConfiguration<\n  Data = any,\n  Error = any,\n  Fn extends Fetcher = BareFetcher\n> {\n  /**\n   *  error retry interval in milliseconds\n   *  @defaultValue 5000\n   */\n  errorRetryInterval: number\n  /** max error retry count */\n  errorRetryCount?: number\n  /**\n   * timeout to trigger the onLoadingSlow event in milliseconds\n   * @defaultValue 3000\n   */\n  loadingTimeout: number\n  /**\n   * only revalidate once during a time span in milliseconds\n   * @defaultValue 5000\n   */\n  focusThrottleInterval: number\n  /**\n   * dedupe requests with the same key in this time span in milliseconds\n   * @defaultValue 2000\n   */\n  dedupingInterval: number\n  /**\n   *  * Disabled by default: `refreshInterval = 0`\n   *  * If set to a number, polling interval in milliseconds\n   *  * If set to a function, the function will receive the latest data and should return the interval in milliseconds\n   *  @see {@link https://swr.vercel.app/docs/revalidation}\n   */\n  refreshInterval?: number | ((latestData: Data | undefined) => number)\n  /**\n   * polling when the window is invisible (if `refreshInterval` is enabled)\n   * @defaultValue false\n   */\n  refreshWhenHidden?: boolean\n  /**\n   * polling when the browser is offline (determined by `navigator.onLine`)\n   *\n   * When enabled, SWR will continue polling even when the browser is offline.\n   * This can be useful for applications that need to check for connectivity\n   * or cache updates while offline.\n   *\n   * @defaultValue false\n   */\n  refreshWhenOffline?: boolean\n  /**\n   * automatically revalidate when window gets focused\n   *\n   * When enabled, SWR will automatically revalidate data when the user\n   * returns focus to the window/tab. This ensures data freshness when\n   * users switch between applications.\n   *\n   * @defaultValue true\n   * @see {@link https://swr.vercel.app/docs/revalidation | Revalidation Documentation}\n   */\n  revalidateOnFocus: boolean\n  /**\n   * automatically revalidate when the browser regains a network connection (via `navigator.onLine`)\n   *\n   * When enabled, SWR will automatically revalidate data when the browser\n   * goes from offline to online state, ensuring data is up-to-date when\n   * connectivity is restored.\n   *\n   * @defaultValue true\n   * @see {@link https://swr.vercel.app/docs/revalidation | Revalidation Documentation}\n   */\n  revalidateOnReconnect: boolean\n  /**\n   * enable or disable automatic revalidation when component is mounted\n   *\n   * Controls whether SWR should automatically fetch data when the component\n   * mounts. When `undefined`, the behavior depends on `revalidateIfStale`.\n   *\n   * @defaultValue undefined (inherits from revalidateIfStale)\n   */\n  revalidateOnMount?: boolean\n  /**\n   * automatically revalidate even if there is stale data\n   * @defaultValue true\n   * @see {@link https://swr.vercel.app/docs/revalidation#disable-automatic-revalidations}\n   */\n  revalidateIfStale: boolean\n  /**\n   * retry when fetcher has an error\n   * @defaultValue true\n   */\n  shouldRetryOnError: boolean | ((err: Error) => boolean)\n  /**\n   * keep the previous result when key is changed but data is not ready\n   * @defaultValue false\n   */\n  keepPreviousData?: boolean\n  /**\n   * @experimental  enable React Suspense mode\n   * @defaultValue false\n   * @see {@link https://swr.vercel.app/docs/suspense}\n   */\n  suspense?: boolean\n  /**\n   * initial data to be returned (note: ***This is per-hook***)\n   * @see {@link https://swr.vercel.app/docs/with-nextjs}\n   */\n  fallbackData?: Data | Promise<Data>\n  /**\n   * warns when preload data is missing for a given key, this includes fallback\n   * data, preload calls, or initial data from the cache provider\n   * @defaultValue false\n   */\n  strictServerPrefetchWarning?: boolean\n  /**\n   * the fetcher function\n   */\n  fetcher?: Fn\n  /**\n   * array of middleware functions\n   * @see {@link https://swr.vercel.app/docs/middleware}\n   */\n  use?: Middleware[]\n  /**\n   * a key-value object of multiple fallback data\n   * @see {@link https://swr.vercel.app/docs/with-nextjs#pre-rendering-with-default-data}\n   */\n  fallback: { [key: string]: any }\n  /**\n   * Function to detect whether pause revalidations, will ignore fetched data and errors when it returns true. Returns false by default.\n   */\n  isPaused: () => boolean\n  /**\n   * callback function when a request takes too long to load (see `loadingTimeout`)\n   */\n  onLoadingSlow: (\n    key: string,\n    config: Readonly<PublicConfiguration<Data, Error, Fn>>\n  ) => void\n  /**\n   * callback function when a request finishes successfully\n   */\n  onSuccess: (\n    data: Data,\n    key: string,\n    config: Readonly<PublicConfiguration<Data, Error, Fn>>\n  ) => void\n  /**\n   * callback function when a request returns an error\n   */\n  onError: (\n    err: Error,\n    key: string,\n    config: Readonly<PublicConfiguration<Data, Error, Fn>>\n  ) => void\n  /**\n   * handler for error retry\n   */\n  onErrorRetry: (\n    err: Error,\n    key: string,\n    config: Readonly<PublicConfiguration<Data, Error, Fn>>,\n    revalidate: Revalidator,\n    revalidateOpts: Required<RevalidatorOptions>\n  ) => void\n  /**\n   * callback function when a request is ignored due to race conditions\n   */\n  onDiscarded: (key: string) => void\n  /**\n   * Comparison function used to detect when returned data has changed, to avoid spurious rerenders. By default, [dequal](https://github.com/lukeed/dequal) is used.\n   */\n  compare: (a: Data | undefined, b: Data | undefined) => boolean\n  /**\n   * IsOnline and isVisible are functions that return a boolean, to determine if the application is \"active\". By default, SWR will bail out a revalidation if these conditions are not met.\n   * @see {@link https://swr.vercel.app/docs/advanced/react-native#customize-focus-and-reconnect-events}\n   */\n  isOnline: () => boolean\n  /**\n   * IsOnline and isVisible are functions that return a boolean, to determine if the application is \"active\". By default, SWR will bail out a revalidation if these conditions are not met.\n   * @see {@link https://swr.vercel.app/docs/advanced/react-native#customize-focus-and-reconnect-events}\n   */\n  isVisible: () => boolean\n}\n\nexport type FullConfiguration<\n  Data = any,\n  Error = any,\n  Fn extends Fetcher = BareFetcher\n> = InternalConfiguration & PublicConfiguration<Data, Error, Fn>\n\n/**\n * Provider configuration for custom focus and reconnect event handling.\n *\n * This configuration allows custom implementations for detecting window focus\n * and network reconnection events. Useful for React Native or other environments\n * where the default browser APIs are not available.\n *\n * @public\n * @see {@link https://swr.vercel.app/docs/advanced/react-native | React Native Documentation}\n */\nexport type ProviderConfiguration = {\n  /**\n   * Initialize focus event listener.\n   *\n   * @param callback - Function to call when window gains focus\n   * @returns Optional cleanup function to remove the listener\n   */\n  initFocus: (callback: () => void) => (() => void) | void\n  /**\n   * Initialize reconnect event listener.\n   *\n   * @param callback - Function to call when network reconnects\n   * @returns Optional cleanup function to remove the listener\n   */\n  initReconnect: (callback: () => void) => (() => void) | void\n}\n\n/**\n * The main useSWR hook interface with multiple overloads for different usage patterns.\n *\n * This interface provides type-safe overloads for various ways to call useSWR,\n * from simple key-only calls to complex configurations with custom fetchers.\n * The overloads ensure proper type inference for data, error, and configuration.\n *\n * @public\n *\n * @example Basic usage\n * ```ts\n * const { data, error } = useSWR('/api/data', fetcher)\n * ```\n *\n * @example With configuration\n * ```ts\n * const { data, error } = useSWR('/api/data', fetcher, {\n *   refreshInterval: 1000,\n *   revalidateOnFocus: false\n * })\n * ```\n *\n * @example Conditional fetching\n * ```ts\n * const { data, error } = useSWR(\n *   user.id ? `/api/user/${user.id}` : null,\n *   fetcher\n * )\n * ```\n *\n * @example Dynamic key with function\n * ```ts\n * const { data, error } = useSWR(\n *   () => user.id ? [`/api/user/${user.id}`, user.token] : null,\n *   ([url, token]) => fetcher(url, { headers: { Authorization: token } })\n * )\n * ```\n */\nexport interface SWRHook {\n  /**\n   * Basic usage with just a key. Requires a global fetcher to be configured,\n   * or can be used for client-side state management without fetching.\n   *\n   * @example\n   * ```ts\n   * // With global fetcher\n   * const { data } = useSWR('/api/user')\n   *\n   * // Client state management\n   * const { data, mutate } = useSWR('user-settings')\n   * mutate({ theme: 'dark' })\n   * ```\n   */\n  <Data = any, Error = any, SWRKey extends Key = StrictKey>(\n    key: SWRKey\n  ): SWRResponse<Data, Error>\n\n  /**\n   * Most common usage pattern with key and explicit fetcher function.\n   * The fetcher receives the key as its argument and returns the data.\n   *\n   * @example\n   * ```ts\n   * const { data, error } = useSWR('/api/user/123',\n   *   (url) => fetch(url).then(res => res.json())\n   * )\n   * ```\n   */\n  <Data = any, Error = any, SWRKey extends Key = StrictKey>(\n    key: SWRKey,\n    fetcher: Fetcher<Data, SWRKey> | null\n  ): SWRResponse<Data, Error>\n\n  /**\n   * Key with fetcher using relaxed key constraints for dynamic or complex keys.\n   * Allows more flexible key types including functions and objects.\n   *\n   * @example\n   * ```ts\n   * const { data } = useSWR(\n   *   () => user ? ['/api/posts', user.id] : null,\n   *   ([url, userId]) => fetchUserPosts(url, userId)\n   * )\n   * ```\n   */\n  <Data = any, Error = any, SWRKey extends Key = Key>(\n    key: SWRKey,\n    fetcher: Fetcher<Data, SWRKey> | null\n  ): SWRResponse<Data, Error>\n\n  /**\n   * Key-only with advanced configuration options and strict typing.\n   * Useful when you need specific SWR options but rely on a global fetcher.\n   *\n   * @example\n   * ```ts\n   * const { data } = useSWR<User>('/api/user', {\n   *   refreshInterval: 5000,\n   *   revalidateOnFocus: false\n   * })\n   * ```\n   */\n  <\n    Data = any,\n    Error = any,\n    SWRKey extends Key = StrictKey,\n    SWROptions extends\n      | SWRConfiguration<Data, Error, Fetcher<Data, SWRKey>>\n      | undefined =\n      | SWRConfiguration<Data, Error, Fetcher<Data, SWRKey>>\n      | undefined\n  >(\n    key: SWRKey\n  ): SWRResponse<Data, Error, SWROptions>\n\n  /**\n   * Key with fetcher and advanced configuration options with strict typing.\n   * Provides full control over fetching behavior and SWR options.\n   *\n   * @example\n   * ```ts\n   * const { data } = useSWR('/api/data', fetcher, {\n   *   suspense: true,\n   *   fallbackData: initialData\n   * })\n   * ```\n   */\n  <\n    Data = any,\n    Error = any,\n    SWRKey extends Key = StrictKey,\n    SWROptions extends\n      | SWRConfiguration<Data, Error, Fetcher<Data, SWRKey>>\n      | undefined =\n      | SWRConfiguration<Data, Error, Fetcher<Data, SWRKey>>\n      | undefined\n  >(\n    key: SWRKey,\n    fetcher: Fetcher<Data, SWRKey> | null\n  ): SWRResponse<Data, Error, SWROptions>\n\n  /**\n   * Key with configuration object but no explicit fetcher. Uses global fetcher\n   * or can be used for pure client state management with configuration.\n   *\n   * @example\n   * ```ts\n   * // With global fetcher and config\n   * const { data } = useSWR('/api/user', {\n   *   refreshInterval: 1000\n   * })\n   *\n   * // Client state with config\n   * const { data } = useSWR('local-state', {\n   *   fallbackData: defaultValue\n   * })\n   * ```\n   */\n  <\n    Data = any,\n    Error = any,\n    SWRKey extends Key = StrictKey,\n    SWROptions extends\n      | SWRConfiguration<Data, Error, Fetcher<Data, SWRKey>>\n      | undefined =\n      | SWRConfiguration<Data, Error, Fetcher<Data, SWRKey>>\n      | undefined\n  >(\n    key: SWRKey,\n    config: SWRConfigurationWithOptionalFallback<SWROptions>\n  ): SWRResponse<Data, Error, SWROptions>\n\n  /**\n   * Complete signature with key, fetcher, and configuration options.\n   * Provides maximum flexibility and control over all SWR behavior.\n   *\n   * @example\n   * ```ts\n   * const { data, error, isLoading } = useSWR(\n   *   '/api/user',\n   *   async (url) => {\n   *     const res = await fetch(url)\n   *     if (!res.ok) throw new Error('Failed to fetch')\n   *     return res.json()\n   *   },\n   *   {\n   *     refreshInterval: 5000,\n   *     onError: (error) => console.error('SWR Error:', error),\n   *     fallbackData: { name: 'Loading...' }\n   *   }\n   * )\n   * ```\n   */\n  <\n    Data = any,\n    Error = any,\n    SWRKey extends Key = StrictKey,\n    SWROptions extends\n      | SWRConfiguration<Data, Error, Fetcher<Data, SWRKey>>\n      | undefined =\n      | SWRConfiguration<Data, Error, Fetcher<Data, SWRKey>>\n      | undefined\n  >(\n    key: SWRKey,\n    fetcher: Fetcher<Data, SWRKey> | null,\n    config: SWRConfigurationWithOptionalFallback<SWROptions>\n  ): SWRResponse<Data, Error, SWROptions>\n\n  /**\n   * Simple key-only usage with flexible key types. Most permissive overload\n   * that accepts any valid key format.\n   *\n   * @example\n   * ```ts\n   * const { data } = useSWR('/api/data')\n   * const { data: userData } = useSWR(['user', userId])\n   * const { data: settings } = useSWR({ endpoint: '/settings', version: 'v1' })\n   * ```\n   */\n  <Data = any, Error = any>(key: Key): SWRResponse<Data, Error>\n\n  /**\n   * Key-only with configuration options using bare fetcher constraints.\n   * Suitable for cases where fetcher type safety is less important.\n   *\n   * @example\n   * ```ts\n   * const { data } = useSWR('/api/data', {\n   *   dedupingInterval: 5000\n   * })\n   * ```\n   */\n  <\n    Data = any,\n    Error = any,\n    SWROptions extends\n      | SWRConfiguration<Data, Error, BareFetcher<Data>>\n      | undefined = SWRConfiguration<Data, Error, BareFetcher<Data>> | undefined\n  >(\n    key: Key\n  ): SWRResponse<Data, Error, SWROptions>\n\n  /**\n   * Key with bare fetcher function that accepts any arguments.\n   * Provides less type safety but maximum flexibility for fetcher signatures.\n   *\n   * @example\n   * ```ts\n   * const { data } = useSWR('/api/user',\n   *   (...args) => customFetcher(...args)\n   * )\n   * ```\n   */\n  <\n    Data = any,\n    Error = any,\n    SWROptions extends\n      | SWRConfiguration<Data, Error, BareFetcher<Data>>\n      | undefined = SWRConfiguration<Data, Error, BareFetcher<Data>> | undefined\n  >(\n    key: Key,\n    fetcher: BareFetcher<Data> | null\n  ): SWRResponse<Data, Error, SWROptions>\n\n  /**\n   * Key with configuration using relaxed fetcher typing constraints.\n   * Useful when working with dynamic or loosely-typed fetcher functions.\n   *\n   * @example\n   * ```ts\n   * const { data } = useSWR(dynamicKey, {\n   *   fetcher: customFetcher,\n   *   refreshInterval: 2000\n   * })\n   * ```\n   */\n  <\n    Data = any,\n    Error = any,\n    SWROptions extends\n      | SWRConfiguration<Data, Error, BareFetcher<Data>>\n      | undefined = SWRConfiguration<Data, Error, BareFetcher<Data>> | undefined\n  >(\n    key: Key,\n    config: SWRConfigurationWithOptionalFallback<SWROptions>\n  ): SWRResponse<Data, Error, SWROptions>\n\n  /**\n   * Complete signature with key, bare fetcher, and configuration.\n   * Most flexible overload with minimal type constraints, suitable for\n   * complex scenarios where strict typing isn't feasible.\n   *\n   * @example\n   * ```ts\n   * const { data } = useSWR(\n   *   complexKey,\n   *   (...args) => legacyFetcher(...args),\n   *   {\n   *     refreshInterval: 10000,\n   *     errorRetryCount: 3\n   *   }\n   * )\n   * ```\n   */\n  <\n    Data = any,\n    Error = any,\n    SWROptions extends\n      | SWRConfiguration<Data, Error, BareFetcher<Data>>\n      | undefined = SWRConfiguration<Data, Error, BareFetcher<Data>> | undefined\n  >(\n    key: Key,\n    fetcher: BareFetcher<Data> | null,\n    config: SWRConfigurationWithOptionalFallback<SWROptions>\n  ): SWRResponse<Data, Error, SWROptions>\n}\n\n/**\n * Middleware function type for extending SWR functionality.\n *\n * Middleware functions receive the next SWR hook in the chain and return\n * a modified hook function. This allows for composition of multiple\n * middleware functions to add features like logging, caching, or\n * request/response transformation.\n *\n * The middleware guarantees that a SWRHook receives a key, fetcher,\n * and config as arguments, providing a consistent interface for\n * middleware authors.\n *\n * @param useSWRNext - The next SWR hook function in the middleware chain\n * @returns A new SWR hook function with middleware functionality applied\n *\n * @template Data - The type of data returned by the fetcher\n * @template Error - The type of error that can be thrown\n *\n * @public\n * @see {@link https://swr.vercel.app/docs/middleware | Middleware Documentation}\n *\n * @example\n * ```ts\n * const logger: Middleware = (useSWRNext) => (key, fetcher, config) => {\n *   console.log('SWR Request:', key)\n *   return useSWRNext(key, fetcher, config)\n * }\n * ```\n */\nexport type Middleware = (\n  useSWRNext: SWRHook\n) => <Data = any, Error = any>(\n  key: Key,\n  fetcher: BareFetcher<Data> | null,\n  config: SWRConfiguration<Data, Error, BareFetcher<Data>>\n) => SWRResponse<Data, Error>\n\n/**\n * Represents a tuple of arguments that can be passed to a fetcher.\n *\n * The first element is typically the primary key (like a URL), followed\n * by additional parameters that affect the request (like query parameters,\n * headers, or request options).\n *\n */\ntype ArgumentsTuple = readonly [any, ...unknown[]]\n\n/**\n * Valid types for SWR keys.\n *\n * SWR keys identify unique requests and can be:\n * - `string`: Simple URL or identifier\n * - `ArgumentsTuple`: Array with URL and additional parameters\n * - `Record<any, any>`: Object that will be serialized\n * - `null | undefined | false`: Falsy values disable the request\n *\n * When a key is falsy, SWR will not make the request, allowing for\n * conditional fetching based on application state.\n *\n * @public\n *\n * @example\n * ```ts\n * // String key\n * useSWR('/api/users', fetcher)\n *\n * // Array key with parameters\n * useSWR(['/api/user', userId], ([url, id]) => fetcher(`${url}/${id}`))\n *\n * // Object key\n * useSWR({ url: '/api/data', params: { page: 1 } }, fetcher)\n *\n * // Conditional key\n * useSWR(userId ? `/api/user/${userId}` : null, fetcher)\n * ```\n */\nexport type Arguments =\n  | string\n  | ArgumentsTuple\n  | Record<any, any>\n  | null\n  | undefined\n  | false\n\n/**\n * SWR key that can be static or a function that returns arguments.\n *\n * When a function is provided, it's called on each render to determine\n * the current key. This allows for dynamic keys based on component state\n * or props.\n *\n * @public\n *\n * @example\n * ```ts\n * // Static key\n * useSWR('/api/data', fetcher)\n *\n * // Dynamic key function\n * useSWR(() => user ? `/api/user/${user.id}` : null, fetcher)\n * ```\n */\nexport type Key = Arguments | (() => Arguments)\n\n/**\n * Strict tuple key type that only allows tuples or falsy values.\n *\n * @internal\n */\nexport type StrictTupleKey = ArgumentsTuple | null | undefined | false\n\n/**\n * Strict key type for internal use.\n *\n * @internal\n */\ntype StrictKey = StrictTupleKey | (() => StrictTupleKey)\n/**\n * Callback function type for mutator operations.\n *\n * This function receives the current cached data and can return new data\n * to update the cache. It can be synchronous or asynchronous, and can\n * return undefined to indicate no change should be made.\n *\n * @template Data - The type of the cached data\n * @param currentData - The current data in the cache (may be undefined)\n * @returns New data to set, undefined for no change, or a Promise resolving to either\n *\n * @public\n *\n * @example\n * ```ts\n * // Increment a counter\n * mutate(key, (current: number = 0) => current + 1)\n *\n * // Async update\n * mutate(key, async (current) => {\n *   const updated = await updateData(current)\n *   return updated\n * })\n * ```\n */\nexport type MutatorCallback<Data = any> = (\n  currentData?: Data\n) => Promise<undefined | Data> | undefined | Data\n\n/**\n * Options for configuring mutator behavior.\n *\n * These options control how the mutation affects the cache, revalidation,\n * and error handling behavior.\n *\n * @template Data - The type of the data related to the key\n * @template MutationData - The type of the data returned by the mutator\n *\n * @public\n */\nexport type MutatorOptions<Data = any, MutationData = Data> = {\n  /**\n   * Whether to revalidate the cache after mutation.\n   *\n   * Can be a boolean or a function that receives the new data and key\n   * to determine whether revalidation should occur.\n   *\n   * @defaultValue true\n   */\n  revalidate?: boolean | ((data: Data, key: Arguments) => boolean)\n\n  /**\n   * Whether and how to populate the cache with mutation results.\n   *\n   * - `false`: Don't update the cache\n   * - `true`: Update cache with mutation result directly\n   * - Function: Transform mutation result before caching\n   *\n   * @defaultValue true\n   */\n  populateCache?:\n    | boolean\n    | ((result: MutationData, currentData: Data | undefined) => Data)\n\n  /**\n   * Optimistic data to show immediately while mutation is pending.\n   *\n   * Can be the data directly or a function that computes it based on\n   * current and displayed data. Useful for immediate UI feedback.\n   *\n   * @defaultValue undefined\n   */\n  optimisticData?:\n    | Data\n    | ((currentData: Data | undefined, displayedData: Data | undefined) => Data)\n\n  /**\n   * Whether to rollback optimistic updates on error.\n   *\n   * Can be a boolean or a function that receives the error to determine\n   * whether rollback should occur.\n   *\n   * @defaultValue true\n   */\n  rollbackOnError?: boolean | ((error: unknown) => boolean)\n\n  /**\n   * Whether to throw errors instead of returning them in the error field.\n   *\n   * When true, errors will be thrown and can be caught with try/catch.\n   * When false, errors are returned in the response object.\n   *\n   * @defaultValue false\n   */\n  throwOnError?: boolean\n}\n\nexport type MutatorConfig = {\n  revalidate?: boolean\n  populateCache?: boolean\n}\n\nexport type Broadcaster<Data = any, Error = any> = (\n  cache: Cache<Data>,\n  key: string,\n  data: Data,\n  error?: Error,\n  isValidating?: boolean,\n  revalidate?: boolean,\n  populateCache?: boolean\n) => Promise<Data>\n\n/**\n * Internal state structure stored in the cache.\n *\n * This represents the complete state for a cache entry, including\n * data, error, and loading states. All fields are optional as they\n * may not be present depending on the request lifecycle.\n *\n * @template Data - The type of data stored\n * @template Error - The type of error that can occur\n *\n * @internal\n */\nexport type State<Data = any, Error = any> = {\n  /** The cached data, if available */\n  data?: Data\n  /** The error object, if an error occurred */\n  error?: Error\n  /** Whether a revalidation is currently in progress */\n  isValidating?: boolean\n  /** Whether this is the initial load with no cached data */\n  isLoading?: boolean\n}\n\nexport type MutatorFn<Data = any> = (\n  cache: Cache,\n  key: Key,\n  data?: Data | Promise<Data> | MutatorCallback<Data>,\n  opts?: boolean | MutatorOptions<Data>\n) => Promise<Data | undefined>\n\nexport type MutatorWrapper<Fn> = Fn extends (\n  ...args: [...infer Parameters]\n) => infer Result\n  ? Parameters[3] extends boolean\n    ? Result\n    : Parameters[3] extends Required<Pick<MutatorOptions, 'populateCache'>>\n    ? Parameters[3]['populateCache'] extends false\n      ? never\n      : Result\n    : Result\n  : never\n\nexport type Mutator<Data = any> = MutatorWrapper<MutatorFn<Data>>\n\nexport interface ScopedMutator {\n  /**\n   * @typeParam Data - The type of the data related to the key\n   * @typeParam MutationData - The type of the data returned by the mutator\n   */\n  <Data = any, MutationData = Data>(\n    matcher: (key?: Arguments) => boolean,\n    data?: MutationData | Promise<MutationData> | MutatorCallback<MutationData>,\n    opts?: boolean | MutatorOptions<Data, MutationData>\n  ): Promise<Array<MutationData | undefined>>\n  /**\n   * @typeParam Data - The type of the data related to the key\n   * @typeParam MutationData - The type of the data returned by the mutator\n   */\n  <Data = any, T = Data>(\n    key: Arguments,\n    data?: T | Promise<T> | MutatorCallback<T>,\n    opts?: boolean | MutatorOptions<Data, T>\n  ): Promise<T | undefined>\n}\n\n/**\n * @typeParam Data - The type of the data related to the key\n * @typeParam MutationData - The type of the data returned by the mutator\n */\nexport type KeyedMutator<Data> = <MutationData = Data>(\n  data?: Data | Promise<Data | undefined> | MutatorCallback<Data>,\n  opts?: boolean | MutatorOptions<Data, MutationData>\n) => Promise<Data | MutationData | undefined>\n\nexport type SWRConfiguration<\n  Data = any,\n  Error = any,\n  Fn extends BareFetcher<any> = BareFetcher<any>\n> = Partial<PublicConfiguration<Data, Error, Fn>> &\n  Partial<ProviderConfiguration> & {\n    provider?: (cache: Readonly<Cache>) => Cache\n  }\n\nexport type IsLoadingResponse<\n  Data = any,\n  Options = SWRDefaultOptions<Data>\n> = SWRGlobalConfig extends { suspense: true }\n  ? Options extends { suspense: true }\n    ? false\n    : false\n  : boolean\n\ntype SWRDefaultOptions<Data> = SWRConfiguration<Data, Error, Fetcher<Data, Key>>\ntype SWRConfigurationWithOptionalFallback<Options> =\n  // If `Options` has `fallbackData`, this turns it to optional instead.\n  Options extends SWRConfiguration &\n    Required<Pick<SWRConfiguration, 'fallbackData'>>\n    ? Omit<Options, 'fallbackData'> & Pick<Partial<Options>, 'fallbackData'>\n    : Options\n\n/**\n * The response object returned by SWR hooks.\n *\n * This interface represents the return value of useSWR and related hooks,\n * providing access to data, error state, and control functions.\n *\n * @template Data - The type of data returned by the fetcher\n * @template Error - The type of error that can be thrown\n * @template Config - The configuration type used to determine blocking behavior\n *\n * @public\n */\nexport interface SWRResponse<Data = any, Error = any, Config = any> {\n  /**\n   * The data returned by the fetcher function.\n   *\n   * - When suspense is enabled or fallbackData is provided: always defined\n   * - Otherwise: `undefined` during initial load or when key is falsy\n   */\n  data: BlockingData<Data, Config> extends true ? Data : Data | undefined\n\n  /**\n   * The error object thrown by the fetcher function.\n   *\n   * `undefined` when there's no error or when a request is in progress.\n   */\n  error: Error | undefined\n\n  /**\n   * Function to mutate the cached data for this specific key.\n   *\n   * This is a bound version of the global mutate function that automatically\n   * uses the current key, providing type safety and convenience.\n   */\n  mutate: KeyedMutator<Data>\n\n  /**\n   * Whether the request is currently being validated (loading fresh data).\n   *\n   * `true` during initial load, revalidation, or when mutate is called\n   * with a promise or async function.\n   */\n  isValidating: boolean\n\n  /**\n   * Whether the request is in initial loading state.\n   *\n   * `true` only during the initial load when there's no cached data.\n   * Unlike `isValidating`, this becomes `false` once data is available.\n   */\n  isLoading: IsLoadingResponse<Data, Config>\n}\n\nexport type KeyLoader<Args extends Arguments = Arguments> =\n  | ((index: number, previousPageData: any | null) => Args)\n  | null\n\nexport interface RevalidatorOptions {\n  retryCount?: number\n  dedupe?: boolean\n}\n\nexport type Revalidator = (\n  revalidateOpts?: RevalidatorOptions\n) => Promise<boolean> | void\n\nexport type RevalidateEvent =\n  | typeof revalidateEvents.FOCUS_EVENT\n  | typeof revalidateEvents.RECONNECT_EVENT\n  | typeof revalidateEvents.MUTATE_EVENT\n  | typeof revalidateEvents.ERROR_REVALIDATE_EVENT\ntype RevalidateCallbackReturnType = {\n  [revalidateEvents.FOCUS_EVENT]: void\n  [revalidateEvents.RECONNECT_EVENT]: void\n  [revalidateEvents.MUTATE_EVENT]: Promise<boolean>\n  [revalidateEvents.ERROR_REVALIDATE_EVENT]: void\n}\nexport type RevalidateCallback = <K extends RevalidateEvent>(\n  type: K,\n  opts?: any\n) => RevalidateCallbackReturnType[K]\n\n/**\n * Cache interface for storing SWR state.\n *\n * This interface defines the contract for cache providers used by SWR.\n * The default implementation uses a Map, but custom cache providers\n * can implement this interface to provide different storage mechanisms.\n *\n * @template Data - The type of data stored in the cache\n *\n * @public\n * @see {@link https://swr.vercel.app/docs/advanced/cache | Cache Documentation}\n */\nexport interface Cache<Data = any> {\n  /**\n   * Get an iterator of all cache keys.\n   *\n   * @returns Iterator that yields all cache keys as strings\n   */\n  keys(): IterableIterator<string>\n\n  /**\n   * Get the cached state for a key.\n   *\n   * @param key - The cache key to look up\n   * @returns The cached state or undefined if not found\n   */\n  get(key: string): State<Data> | undefined\n\n  /**\n   * Set the cached state for a key.\n   *\n   * @param key - The cache key to set\n   * @param value - The state to cache\n   */\n  set(key: string, value: State<Data>): void\n\n  /**\n   * Delete a cached entry.\n   *\n   * @param key - The cache key to delete\n   */\n  delete(key: string): void\n}\n\nexport interface StateDependencies {\n  data?: boolean\n  error?: boolean\n  isValidating?: boolean\n  isLoading?: boolean\n}\n"
  },
  {
    "path": "src/_internal/utils/cache.ts",
    "content": "import { defaultConfigOptions } from './web-preset'\nimport { IS_SERVER } from './env'\nimport { UNDEFINED, mergeObjects, noop } from './shared'\nimport { internalMutate } from './mutate'\nimport { SWRGlobalState } from './global-state'\nimport * as revalidateEvents from '../events'\n\nimport type {\n  Cache,\n  ScopedMutator,\n  RevalidateEvent,\n  RevalidateCallback,\n  ProviderConfiguration,\n  GlobalState\n} from '../types'\n\nconst revalidateAllKeys = (\n  revalidators: Record<string, RevalidateCallback[]>,\n  type: RevalidateEvent\n) => {\n  for (const key in revalidators) {\n    if (revalidators[key][0]) revalidators[key][0](type)\n  }\n}\n\nexport const initCache = <Data = any>(\n  provider: Cache<Data>,\n  options?: Partial<ProviderConfiguration>\n):\n  | [Cache<Data>, ScopedMutator, () => void, () => void]\n  | [Cache<Data>, ScopedMutator]\n  | undefined => {\n  // The global state for a specific provider will be used to deduplicate\n  // requests and store listeners. As well as a mutate function that is bound to\n  // the cache.\n\n  // The provider's global state might be already initialized. Let's try to get the\n  // global state associated with the provider first.\n  if (!SWRGlobalState.has(provider)) {\n    const opts = mergeObjects(defaultConfigOptions, options)\n\n    // If there's no global state bound to the provider, create a new one with the\n    // new mutate function.\n    const EVENT_REVALIDATORS = Object.create(null)\n\n    const mutate = internalMutate.bind(UNDEFINED, provider) as ScopedMutator\n    let unmount = noop\n\n    const subscriptions: Record<string, ((current: any, prev: any) => void)[]> =\n      Object.create(null)\n    const subscribe = (\n      key: string,\n      callback: (current: any, prev: any) => void\n    ) => {\n      const subs = subscriptions[key] || []\n      subscriptions[key] = subs\n\n      subs.push(callback)\n      return () => subs.splice(subs.indexOf(callback), 1)\n    }\n    const setter = (key: string, value: any, prev: any) => {\n      provider.set(key, value)\n      const subs = subscriptions[key]\n      if (subs) {\n        for (const fn of subs) {\n          fn(value, prev)\n        }\n      }\n    }\n\n    const initProvider = () => {\n      if (!SWRGlobalState.has(provider)) {\n        // Update the state if it's new, or if the provider has been extended.\n        SWRGlobalState.set(provider, [\n          EVENT_REVALIDATORS,\n          Object.create(null),\n          Object.create(null),\n          Object.create(null),\n          mutate,\n          setter,\n          subscribe\n        ])\n        if (!IS_SERVER) {\n          // When listening to the native events for auto revalidations,\n          // we intentionally put a delay (setTimeout) here to make sure they are\n          // fired after immediate JavaScript executions, which can be\n          // React's state updates.\n          // This avoids some unnecessary revalidations such as\n          // https://github.com/vercel/swr/issues/1680.\n          const releaseFocus = opts.initFocus(\n            setTimeout.bind(\n              UNDEFINED,\n              revalidateAllKeys.bind(\n                UNDEFINED,\n                EVENT_REVALIDATORS,\n                revalidateEvents.FOCUS_EVENT\n              )\n            )\n          )\n          const releaseReconnect = opts.initReconnect(\n            setTimeout.bind(\n              UNDEFINED,\n              revalidateAllKeys.bind(\n                UNDEFINED,\n                EVENT_REVALIDATORS,\n                revalidateEvents.RECONNECT_EVENT\n              )\n            )\n          )\n          unmount = () => {\n            // eslint-disable-next-line @typescript-eslint/no-unused-expressions\n            releaseFocus && releaseFocus()\n            // eslint-disable-next-line @typescript-eslint/no-unused-expressions\n            releaseReconnect && releaseReconnect()\n            // When un-mounting, we need to remove the cache provider from the state\n            // storage too because it's a side-effect. Otherwise, when re-mounting we\n            // will not re-register those event listeners.\n            SWRGlobalState.delete(provider)\n          }\n        }\n      }\n    }\n    initProvider()\n\n    // This is a new provider, we need to initialize it and setup DOM events\n    // listeners for `focus` and `reconnect` actions.\n\n    // We might want to inject an extra layer on top of `provider` in the future,\n    // such as key serialization, auto GC, etc.\n    // For now, it's just a `Map` interface without any modifications.\n    return [provider, mutate, initProvider, unmount]\n  }\n\n  return [provider, (SWRGlobalState.get(provider) as GlobalState)[4]]\n}\n"
  },
  {
    "path": "src/_internal/utils/config-context.ts",
    "content": "'use client'\n\nimport type { FC, PropsWithChildren } from 'react'\nimport {\n  createContext,\n  createElement,\n  useContext,\n  useMemo,\n  useRef\n} from 'react'\nimport { cache as defaultCache } from './config'\nimport { initCache } from './cache'\nimport { mergeConfigs } from './merge-config'\nimport { UNDEFINED, mergeObjects, isFunction } from './shared'\nimport { useIsomorphicLayoutEffect } from './env'\nimport type { SWRConfiguration, FullConfiguration } from '../types'\n\nexport const SWRConfigContext = createContext<Partial<FullConfiguration>>({})\n\nconst SWRConfig: FC<\n  PropsWithChildren<{\n    value?:\n      | SWRConfiguration\n      | ((parentConfig?: SWRConfiguration) => SWRConfiguration)\n  }>\n> = props => {\n  const { value } = props\n  const parentConfig = useContext(SWRConfigContext)\n  const isFunctionalConfig = isFunction(value)\n  const config = useMemo(\n    () => (isFunctionalConfig ? value(parentConfig) : value),\n    [isFunctionalConfig, parentConfig, value]\n  )\n  // Extend parent context values and middleware.\n  const extendedConfig = useMemo(\n    () => (isFunctionalConfig ? config : mergeConfigs(parentConfig, config)),\n    [isFunctionalConfig, parentConfig, config]\n  )\n\n  // Should not use the inherited provider.\n  const provider = config && config.provider\n\n  // initialize the cache only on first access.\n  const cacheContextRef = useRef<ReturnType<typeof initCache>>(UNDEFINED)\n  if (provider && !cacheContextRef.current) {\n    cacheContextRef.current = initCache(\n      provider((extendedConfig as any).cache || defaultCache),\n      config\n    )\n  }\n  const cacheContext = cacheContextRef.current\n\n  // Override the cache if a new provider is given.\n  if (cacheContext) {\n    ;(extendedConfig as any).cache = cacheContext[0]\n    ;(extendedConfig as any).mutate = cacheContext[1]\n  }\n\n  // Unsubscribe events.\n  useIsomorphicLayoutEffect(() => {\n    if (cacheContext) {\n      // eslint-disable-next-line @typescript-eslint/no-unused-expressions\n      cacheContext[2] && cacheContext[2]()\n      return cacheContext[3]\n    }\n  }, [])\n\n  return createElement(\n    SWRConfigContext.Provider,\n    mergeObjects(props, {\n      value: extendedConfig\n    })\n  )\n}\n\nexport default SWRConfig\n"
  },
  {
    "path": "src/_internal/utils/config.ts",
    "content": "import type {\n  PublicConfiguration,\n  FullConfiguration,\n  RevalidatorOptions,\n  Revalidator,\n  ScopedMutator,\n  Cache\n} from '../types'\n\nimport { initCache } from './cache'\nimport { preset } from './web-preset'\nimport { slowConnection } from './env'\nimport { isUndefined, noop, mergeObjects } from './shared'\n\nimport { dequal } from 'dequal/lite'\n\n// error retry\nconst onErrorRetry = (\n  _: unknown,\n  __: string,\n  config: Readonly<PublicConfiguration>,\n  revalidate: Revalidator,\n  opts: Required<RevalidatorOptions>\n): void => {\n  const maxRetryCount = config.errorRetryCount\n  const currentRetryCount = opts.retryCount\n\n  // Exponential backoff\n  const timeout =\n    ~~(\n      (Math.random() + 0.5) *\n      (1 << (currentRetryCount < 8 ? currentRetryCount : 8))\n    ) * config.errorRetryInterval\n\n  if (!isUndefined(maxRetryCount) && currentRetryCount > maxRetryCount) {\n    return\n  }\n\n  setTimeout(revalidate, timeout, opts)\n}\n\nconst compare = dequal\n\n// Default cache provider\nconst [cache, mutate] = initCache(new Map()) as [Cache<any>, ScopedMutator]\nexport { cache, mutate, compare }\n\n// Default config\nexport const defaultConfig: FullConfiguration = mergeObjects(\n  {\n    // events\n    onLoadingSlow: noop,\n    onSuccess: noop,\n    onError: noop,\n    onErrorRetry,\n    onDiscarded: noop,\n\n    // switches\n    revalidateOnFocus: true,\n    revalidateOnReconnect: true,\n    revalidateIfStale: true,\n    shouldRetryOnError: true,\n\n    // timeouts\n    errorRetryInterval: slowConnection ? 10000 : 5000,\n    focusThrottleInterval: 5 * 1000,\n    dedupingInterval: 2 * 1000,\n    loadingTimeout: slowConnection ? 5000 : 3000,\n\n    // providers\n    compare,\n    isPaused: () => false,\n    cache,\n    mutate,\n    fallback: {}\n  },\n  // use web preset by default\n  preset\n)\n"
  },
  {
    "path": "src/_internal/utils/devtools.ts",
    "content": "import React from 'react'\nimport { isWindowDefined } from './helper'\n\n// @ts-expect-error\nconst enableDevtools = isWindowDefined && window.__SWR_DEVTOOLS_USE__\n\nexport const use = enableDevtools\n  ? // @ts-expect-error\n    window.__SWR_DEVTOOLS_USE__\n  : []\n\nexport const setupDevTools = () => {\n  if (enableDevtools) {\n    // @ts-expect-error\n    window.__SWR_DEVTOOLS_REACT__ = React\n  }\n}\n"
  },
  {
    "path": "src/_internal/utils/env.ts",
    "content": "import React, { useEffect, useLayoutEffect } from 'react'\nimport { hasRequestAnimationFrame, isLegacyDeno, isWindowDefined } from './helper'\n\nexport const IS_REACT_LEGACY = !React.useId\n\nexport const IS_SERVER = !isWindowDefined || isLegacyDeno\n\n// Polyfill requestAnimationFrame\nexport const rAF = (\n  f: (...args: any[]) => void\n): number | ReturnType<typeof setTimeout> =>\n  hasRequestAnimationFrame()\n    ? window['requestAnimationFrame'](f)\n    : setTimeout(f, 1)\n\n// React currently throws a warning when using useLayoutEffect on the server.\n// To get around it, we can conditionally useEffect on the server (no-op) and\n// useLayoutEffect in the browser.\nexport const useIsomorphicLayoutEffect = IS_SERVER ? useEffect : useLayoutEffect\n\n// This assignment is to extend the Navigator type to use effectiveType.\nconst navigatorConnection =\n  typeof navigator !== 'undefined' &&\n  (\n    navigator as Navigator & {\n      connection?: {\n        effectiveType: string\n        saveData: boolean\n      }\n    }\n  ).connection\n\n// Adjust the config based on slow connection status (<= 70Kbps).\nexport const slowConnection =\n  !IS_SERVER &&\n  navigatorConnection &&\n  (['slow-2g', '2g'].includes(navigatorConnection.effectiveType) ||\n    navigatorConnection.saveData)\n"
  },
  {
    "path": "src/_internal/utils/global-state.ts",
    "content": "import type { Cache, GlobalState } from '../types'\n\n// Global state used to deduplicate requests and store listeners\nexport const SWRGlobalState = new WeakMap<Cache, GlobalState>()\n"
  },
  {
    "path": "src/_internal/utils/hash.ts",
    "content": "import { OBJECT, isUndefined } from './shared'\n\n// use WeakMap to store the object->key mapping\n// so the objects can be garbage collected.\n// WeakMap uses a hashtable under the hood, so the lookup\n// complexity is almost O(1).\nconst table = new WeakMap<object, number | string>()\n\nconst getTypeName = (value: any) => OBJECT.prototype.toString.call(value)\n\nconst isObjectTypeName = (typeName: string, type: string) =>\n  typeName === `[object ${type}]`\n\n// counter of the key\nlet counter = 0\n\n// A stable hash implementation that supports:\n// - Fast and ensures unique hash properties\n// - Handles unserializable values\n// - Handles object key ordering\n// - Generates short results\n//\n// This is not a serialization function, and the result is not guaranteed to be\n// parsable.\nexport const stableHash = (arg: any): string => {\n  const type = typeof arg\n  const typeName = getTypeName(arg)\n  const isDate = isObjectTypeName(typeName, 'Date')\n  const isRegex = isObjectTypeName(typeName, 'RegExp')\n  const isPlainObject = isObjectTypeName(typeName, 'Object')\n  let result: any\n  let index: any\n\n  if (OBJECT(arg) === arg && !isDate && !isRegex) {\n    // Object/function, not null/date/regexp. Use WeakMap to store the id first.\n    // If it's already hashed, directly return the result.\n    result = table.get(arg)\n    if (result) return result\n\n    // Store the hash first for circular reference detection before entering the\n    // recursive `stableHash` calls.\n    // For other objects like set and map, we use this id directly as the hash.\n    result = ++counter + '~'\n    table.set(arg, result)\n\n    if (Array.isArray(arg)) {\n      // Array.\n      result = '@'\n      for (index = 0; index < arg.length; index++) {\n        result += stableHash(arg[index]) + ','\n      }\n      table.set(arg, result)\n    }\n    if (isPlainObject) {\n      // Object, sort keys.\n      result = '#'\n      const keys = OBJECT.keys(arg).sort()\n      while (!isUndefined((index = keys.pop() as string))) {\n        if (!isUndefined(arg[index])) {\n          result += index + ':' + stableHash(arg[index]) + ','\n        }\n      }\n      table.set(arg, result)\n    }\n  } else {\n    result = isDate\n      ? arg.toJSON()\n      : type == 'symbol'\n      ? arg.toString()\n      : type == 'string'\n      ? JSON.stringify(arg)\n      : '' + arg\n  }\n\n  return result\n}\n"
  },
  {
    "path": "src/_internal/utils/helper.ts",
    "content": "import type { Cache, State, GlobalState } from '../types'\nimport { SWRGlobalState } from './global-state'\nimport { isUndefined, mergeObjects } from './shared'\n\nconst EMPTY_CACHE = {}\nconst INITIAL_CACHE: Record<string, any> = {}\n\nconst STR_UNDEFINED = 'undefined'\n\n// NOTE: Use the function to guarantee it's re-evaluated between jsdom and node runtime for tests.\nexport const isWindowDefined = typeof window != STR_UNDEFINED\nexport const isDocumentDefined = typeof document != STR_UNDEFINED\nexport const isLegacyDeno = isWindowDefined && 'Deno' in window\n\nexport const hasRequestAnimationFrame = () =>\n  isWindowDefined && typeof window['requestAnimationFrame'] != STR_UNDEFINED\n\nexport const createCacheHelper = <Data = any, T = State<Data, any>>(\n  cache: Cache,\n  key: string | undefined\n) => {\n  const state = SWRGlobalState.get(cache) as GlobalState\n  return [\n    // Getter\n    () => ((!isUndefined(key) && cache.get(key)) || EMPTY_CACHE) as T,\n    // Setter\n    (info: T) => {\n      if (!isUndefined(key)) {\n        const prev = cache.get(key)\n\n        // Before writing to the store, we keep the value in the initial cache\n        // if it's not there yet.\n        if (!(key in INITIAL_CACHE)) {\n          INITIAL_CACHE[key] = prev\n        }\n\n        state[5](key, mergeObjects(prev, info), prev || EMPTY_CACHE)\n      }\n    },\n    // Subscriber\n    state[6],\n    // Get server cache snapshot\n    () => {\n      if (!isUndefined(key)) {\n        // If the cache was updated on the client, we return the stored initial value.\n        if (key in INITIAL_CACHE) return INITIAL_CACHE[key]\n      }\n\n      // If we haven't done any client-side updates, we return the current value.\n      return ((!isUndefined(key) && cache.get(key)) || EMPTY_CACHE) as T\n    }\n  ] as const\n}\n\n// export { UNDEFINED, OBJECT, isUndefined, isFunction, mergeObjects, isPromiseLike }\n"
  },
  {
    "path": "src/_internal/utils/merge-config.ts",
    "content": "import { mergeObjects } from './shared'\nimport type { FullConfiguration } from '../types'\n\nexport const mergeConfigs = (\n  a: Partial<FullConfiguration>,\n  b?: Partial<FullConfiguration>\n) => {\n  // Need to create a new object to avoid mutating the original here.\n  const v: Partial<FullConfiguration> = mergeObjects(a, b)\n\n  // If two configs are provided, merge their `use` and `fallback` options.\n  if (b) {\n    const { use: u1, fallback: f1 } = a\n    const { use: u2, fallback: f2 } = b\n    if (u1 && u2) {\n      v.use = u1.concat(u2)\n    }\n    if (f1 && f2) {\n      v.fallback = mergeObjects(f1, f2)\n    }\n  }\n\n  return v\n}\n"
  },
  {
    "path": "src/_internal/utils/middleware-preset.ts",
    "content": "import { use as devtoolsUse } from './devtools'\nimport { middleware as preload } from './preload'\n\nexport const BUILT_IN_MIDDLEWARE = devtoolsUse.concat(preload)\n"
  },
  {
    "path": "src/_internal/utils/mutate.ts",
    "content": "import { serialize } from './serialize'\nimport { createCacheHelper } from './helper'\nimport {\n  isFunction,\n  isUndefined,\n  UNDEFINED,\n  mergeObjects,\n  isPromiseLike\n} from './shared'\nimport { SWRGlobalState } from './global-state'\nimport { getTimestamp } from './timestamp'\nimport * as revalidateEvents from '../events'\nimport type {\n  Cache,\n  MutatorCallback,\n  MutatorOptions,\n  GlobalState,\n  State,\n  Arguments,\n  Key\n} from '../types'\n\ntype KeyFilter = (key?: Arguments) => boolean\ntype MutateState<Data> = State<Data, any> & {\n  // The previously committed data.\n  _c?: Data\n}\n\nexport async function internalMutate<Data>(\n  cache: Cache,\n  _key: KeyFilter,\n  _data?: Data | Promise<Data | undefined> | MutatorCallback<Data>,\n  _opts?: boolean | MutatorOptions<Data>\n): Promise<Array<Data | undefined>>\nexport async function internalMutate<Data>(\n  cache: Cache,\n  _key: Arguments,\n  _data?: Data | Promise<Data | undefined> | MutatorCallback<Data>,\n  _opts?: boolean | MutatorOptions<Data>\n): Promise<Data | undefined>\nexport async function internalMutate<Data>(\n  ...args: [\n    cache: Cache,\n    _key: KeyFilter | Arguments,\n    _data?: Data | Promise<Data | undefined> | MutatorCallback<Data>,\n    _opts?: boolean | MutatorOptions<Data>\n  ]\n): Promise<any> {\n  const [cache, _key, _data, _opts] = args\n\n  // When passing as a boolean, it's explicitly used to disable/enable\n  // revalidation.\n  const options = mergeObjects(\n    { populateCache: true, throwOnError: true },\n    typeof _opts === 'boolean' ? { revalidate: _opts } : _opts || {}\n  )\n\n  let populateCache = options.populateCache\n\n  const rollbackOnErrorOption = options.rollbackOnError\n  let optimisticData = options.optimisticData\n\n  const rollbackOnError = (error: unknown): boolean => {\n    return typeof rollbackOnErrorOption === 'function'\n      ? rollbackOnErrorOption(error)\n      : rollbackOnErrorOption !== false\n  }\n  const throwOnError = options.throwOnError\n\n  // If the second argument is a key filter, return the mutation results for all\n  // filtered keys.\n  if (isFunction(_key)) {\n    const keyFilter = _key\n    const matchedKeys: Key[] = []\n    const it = cache.keys()\n    for (const key of it) {\n      if (\n        // Skip the special useSWRInfinite and useSWRSubscription keys.\n        !/^\\$(inf|sub)\\$/.test(key) &&\n        keyFilter((cache.get(key) as { _k: Arguments })._k)\n      ) {\n        matchedKeys.push(key)\n      }\n    }\n    return Promise.all(matchedKeys.map(mutateByKey))\n  }\n\n  return mutateByKey(_key)\n\n  async function mutateByKey(_k: Key): Promise<Data | undefined> {\n    // Serialize key\n    const [key] = serialize(_k)\n    if (!key) return\n    const [get, set] = createCacheHelper<Data, MutateState<Data>>(cache, key)\n    const [EVENT_REVALIDATORS, MUTATION, FETCH, PRELOAD] = SWRGlobalState.get(\n      cache\n    ) as GlobalState\n\n    const startRevalidate = () => {\n      const revalidators = EVENT_REVALIDATORS[key]\n      const revalidate = isFunction(options.revalidate)\n        ? options.revalidate(get().data, _k)\n        : options.revalidate !== false\n      if (revalidate) {\n        // Invalidate the key by deleting the concurrent request markers so new\n        // requests will not be deduped.\n        delete FETCH[key]\n        delete PRELOAD[key]\n        if (revalidators && revalidators[0]) {\n          return revalidators[0](revalidateEvents.MUTATE_EVENT).then(\n            () => get().data\n          )\n        }\n      }\n      return get().data\n    }\n\n    // If there is no new data provided, revalidate the key with current state.\n    if (args.length < 3) {\n      // Revalidate and broadcast state.\n      return startRevalidate()\n    }\n\n    let data: any = _data\n    let error: unknown\n    let isError = false\n\n    // Update global timestamps.\n    const beforeMutationTs = getTimestamp()\n    MUTATION[key] = [beforeMutationTs, 0]\n\n    const hasOptimisticData = !isUndefined(optimisticData)\n    const state = get()\n\n    // `displayedData` is the current value on screen. It could be the optimistic value\n    // that is going to be overridden by a `committedData`, or get reverted back.\n    // `committedData` is the validated value that comes from a fetch or mutation.\n    const displayedData = state.data\n    const currentData = state._c\n    const committedData = isUndefined(currentData) ? displayedData : currentData\n\n    // Do optimistic data update.\n    if (hasOptimisticData) {\n      optimisticData = isFunction(optimisticData)\n        ? optimisticData(committedData, displayedData)\n        : optimisticData\n\n      // When we set optimistic data, backup the current committedData data in `_c`.\n      set({ data: optimisticData, _c: committedData })\n    }\n\n    if (isFunction(data)) {\n      // `data` is a function, call it passing current cache value.\n      try {\n        data = (data as MutatorCallback<Data>)(committedData)\n      } catch (err) {\n        // If it throws an error synchronously, we shouldn't update the cache.\n        error = err\n        isError = true\n      }\n    }\n\n    // `data` is a promise/thenable, resolve the final data first.\n    if (data && isPromiseLike(data)) {\n      // This means that the mutation is async, we need to check timestamps to\n      // avoid race conditions.\n      data = await (data as Promise<Data>).catch(err => {\n        error = err\n        isError = true\n      })\n\n      // Check if other mutations have occurred since we've started this mutation.\n      // If there's a race we don't update cache or broadcast the change,\n      // just return the data.\n      if (beforeMutationTs !== MUTATION[key][0]) {\n        if (isError) throw error\n        return data\n      } else if (isError && hasOptimisticData && rollbackOnError(error)) {\n        // Rollback. Always populate the cache in this case but without\n        // transforming the data.\n        populateCache = true\n\n        // Reset data to be the latest committed data, and clear the `_c` value.\n        set({ data: committedData, _c: UNDEFINED })\n      }\n    }\n\n    // If we should write back the cache after request.\n    if (populateCache) {\n      if (!isError) {\n        // Transform the result into data.\n        if (isFunction(populateCache)) {\n          const populateCachedData = populateCache(data, committedData)\n          set({ data: populateCachedData, error: UNDEFINED, _c: UNDEFINED })\n        } else {\n          // Only update cached data and reset the error if there's no error. Data can be `undefined` here.\n          set({ data, error: UNDEFINED, _c: UNDEFINED })\n        }\n      }\n    }\n\n    // Reset the timestamp to mark the mutation has ended.\n    MUTATION[key][1] = getTimestamp()\n\n    // Update existing SWR Hooks' internal states:\n    Promise.resolve(startRevalidate()).then(() => {\n      // The mutation and revalidation are ended, we can clear it since the data is\n      // not an optimistic value anymore.\n      set({ _c: UNDEFINED })\n    })\n\n    // Throw error or return data\n    if (isError) {\n      if (throwOnError) throw error\n      return\n    }\n    return data\n  }\n}\n"
  },
  {
    "path": "src/_internal/utils/normalize-args.ts",
    "content": "import { isFunction } from './shared'\n\nimport type { Key, Fetcher, SWRConfiguration } from '../types'\n\nexport const normalize = <KeyType = Key, Data = any>(\n  args:\n    | [KeyType]\n    | [KeyType, Fetcher<Data> | null]\n    | [KeyType, SWRConfiguration | undefined]\n    | [KeyType, Fetcher<Data> | null, SWRConfiguration | undefined]\n): [KeyType, Fetcher<Data> | null, Partial<SWRConfiguration<Data>>] => {\n  return isFunction(args[1])\n    ? [args[0], args[1], args[2] || {}]\n    : [args[0], null, (args[1] === null ? args[2] : args[1]) || {}]\n}\n"
  },
  {
    "path": "src/_internal/utils/preload.ts",
    "content": "import type {\n  Middleware,\n  Key,\n  BareFetcher,\n  GlobalState,\n  FetcherResponse\n} from '../types'\nimport { serialize } from './serialize'\nimport { cache } from './config'\nimport { SWRGlobalState } from './global-state'\nimport { isUndefined } from './shared'\nimport { INFINITE_PREFIX } from '../constants'\nimport { IS_SERVER } from './env'\n// Basically same as Fetcher but without Conditional Fetching\ntype PreloadFetcher<\n  Data = unknown,\n  SWRKey extends Key = Key\n> = SWRKey extends () => infer Arg\n  ? (arg: Arg) => FetcherResponse<Data>\n  : SWRKey extends infer Arg\n  ? (arg: Arg) => FetcherResponse<Data>\n  : never\n\nexport const preload = <\n  Data = any,\n  SWRKey extends Key = Key,\n  Fetcher extends BareFetcher = PreloadFetcher<Data, SWRKey>\n>(\n  key_: SWRKey,\n  fetcher: Fetcher\n): ReturnType<Fetcher> => {\n  // preload should be a no-op on the server\n  if (IS_SERVER) {\n    return undefined as ReturnType<Fetcher>\n  }\n\n  const [key, fnArg] = serialize(key_)\n  const [, , , PRELOAD] = SWRGlobalState.get(cache) as GlobalState\n\n  // Prevent preload to be called multiple times before used.\n  if (PRELOAD[key]) return PRELOAD[key]\n\n  const req = fetcher(fnArg) as ReturnType<Fetcher>\n  PRELOAD[key] = req\n  return req\n}\n\nexport const middleware: Middleware =\n  useSWRNext => (key_, fetcher_, config) => {\n    // fetcher might be a sync function, so this should not be an async function\n    const fetcher =\n      fetcher_ &&\n      ((...args: any[]) => {\n        const [key] = serialize(key_)\n        const [, , , PRELOAD] = SWRGlobalState.get(cache) as GlobalState\n\n        if (key.startsWith(INFINITE_PREFIX)) {\n          // we want the infinite fetcher to be called.\n          // handling of the PRELOAD cache happens there.\n          return fetcher_(...args)\n        }\n\n        const req = PRELOAD[key]\n        if (isUndefined(req)) return fetcher_(...args)\n        delete PRELOAD[key]\n        return req\n      })\n    return useSWRNext(key_, fetcher, config)\n  }\n"
  },
  {
    "path": "src/_internal/utils/resolve-args.ts",
    "content": "import { mergeConfigs } from './merge-config'\nimport { normalize } from './normalize-args'\nimport { useSWRConfig } from './use-swr-config'\nimport { BUILT_IN_MIDDLEWARE } from './middleware-preset'\n\n// It's tricky to pass generic types as parameters, so we just directly override\n// the types here.\nexport const withArgs = <SWRType>(hook: any) => {\n  return function useSWRArgs(...args: any) {\n    // Get the default and inherited configuration.\n    const fallbackConfig = useSWRConfig()\n\n    // Normalize arguments.\n    const [key, fn, _config] = normalize<any, any>(args)\n\n    // Merge configurations.\n    const config = mergeConfigs(fallbackConfig, _config)\n\n    // Apply middleware\n    let next = hook\n    const { use } = config\n    const middleware = (use || []).concat(BUILT_IN_MIDDLEWARE)\n    for (let i = middleware.length; i--; ) {\n      next = middleware[i](next)\n    }\n\n    return next(key, fn || config.fetcher || null, config)\n  } as unknown as SWRType\n}\n"
  },
  {
    "path": "src/_internal/utils/serialize.ts",
    "content": "import { stableHash } from './hash'\nimport { isFunction } from './shared'\n\nimport type { Key, Arguments } from '../types'\n\nexport const serialize = (key: Key): [string, Arguments] => {\n  if (isFunction(key)) {\n    try {\n      key = key()\n    } catch (err) {\n      // dependencies not ready\n      key = ''\n    }\n  }\n\n  // Use the original key as the argument of fetcher. This can be a string or an\n  // array of values.\n  const args = key\n\n  // If key is not falsy, or not an empty array, hash it.\n  key =\n    typeof key == 'string'\n      ? key\n      : (Array.isArray(key) ? key.length : key)\n      ? stableHash(key)\n      : ''\n\n  return [key, args]\n}\n"
  },
  {
    "path": "src/_internal/utils/shared.ts",
    "content": "// Shared state between server components and client components\n\nexport const noop = () => {}\n\n// Using noop() as the undefined value as undefined can be replaced\n// by something else. Prettier ignore and extra parentheses are necessary here\n// to ensure that tsc doesn't remove the __NOINLINE__ comment.\n// prettier-ignore\nexport const UNDEFINED = (/*#__NOINLINE__*/ noop()) as undefined\n\nexport const OBJECT = Object\n\nexport const isUndefined = (v: any): v is undefined => v === UNDEFINED\nexport const isFunction = <\n  T extends (...args: any[]) => any = (...args: any[]) => any\n>(\n  v: unknown\n): v is T => typeof v == 'function'\nexport const mergeObjects = (a: any, b?: any) => ({ ...a, ...b })\nexport const isPromiseLike = (x: unknown): x is PromiseLike<unknown> =>\n  isFunction((x as any).then)\n"
  },
  {
    "path": "src/_internal/utils/subscribe-key.ts",
    "content": "type Callback = (...args: any[]) => any\n\n// Add a callback function to a list of keyed callback functions and return\n// the unsubscribe function.\nexport const subscribeCallback = (\n  key: string,\n  callbacks: Record<string, Callback[]>,\n  callback: Callback\n) => {\n  const keyedRevalidators = callbacks[key] || (callbacks[key] = [])\n  keyedRevalidators.push(callback)\n\n  return () => {\n    const index = keyedRevalidators.indexOf(callback)\n\n    if (index >= 0) {\n      // O(1): faster than splice\n      keyedRevalidators[index] = keyedRevalidators[keyedRevalidators.length - 1]\n      keyedRevalidators.pop()\n    }\n  }\n}\n"
  },
  {
    "path": "src/_internal/utils/timestamp.ts",
    "content": "// Global timestamp.\nlet __timestamp = 0\n\nexport const getTimestamp = () => ++__timestamp\n"
  },
  {
    "path": "src/_internal/utils/use-swr-config.ts",
    "content": "import { useContext, useMemo } from 'react'\nimport { defaultConfig } from './config'\nimport { SWRConfigContext } from './config-context'\nimport { mergeObjects } from './shared'\nimport type { FullConfiguration } from '../types'\n\nexport const useSWRConfig = (): FullConfiguration => {\n  const parentConfig = useContext(SWRConfigContext)\n  const mergedConfig = useMemo(\n    () => mergeObjects(defaultConfig, parentConfig),\n    [parentConfig]\n  )\n  return mergedConfig\n}\n"
  },
  {
    "path": "src/_internal/utils/web-preset.ts",
    "content": "import type { ProviderConfiguration } from '../types'\nimport { isWindowDefined, isDocumentDefined } from './helper'\nimport { isUndefined, noop } from './shared'\n\n/**\n * Due to the bug https://bugs.chromium.org/p/chromium/issues/detail?id=678075,\n * it's not reliable to detect if the browser is currently online or offline\n * based on `navigator.onLine`.\n * As a workaround, we always assume it's online on the first load, and change\n * the status upon `online` or `offline` events.\n */\nlet online = true\nconst isOnline = () => online\n\n// For node and React Native, `add/removeEventListener` doesn't exist on window.\nconst [onWindowEvent, offWindowEvent] =\n  isWindowDefined && window.addEventListener\n    ? [\n        window.addEventListener.bind(window),\n        window.removeEventListener.bind(window)\n      ]\n    : [noop, noop]\n\nconst isVisible = () => {\n  const visibilityState = isDocumentDefined && document.visibilityState\n  return isUndefined(visibilityState) || visibilityState !== 'hidden'\n}\n\nconst initFocus = (callback: () => void) => {\n  // focus revalidate\n  if (isDocumentDefined) {\n    document.addEventListener('visibilitychange', callback)\n  }\n  onWindowEvent('focus', callback)\n  return () => {\n    if (isDocumentDefined) {\n      document.removeEventListener('visibilitychange', callback)\n    }\n    offWindowEvent('focus', callback)\n  }\n}\n\nconst initReconnect = (callback: () => void) => {\n  // revalidate on reconnected\n  const onOnline = () => {\n    online = true\n    callback()\n  }\n  // nothing to revalidate, just update the status\n  const onOffline = () => {\n    online = false\n  }\n  onWindowEvent('online', onOnline)\n  onWindowEvent('offline', onOffline)\n  return () => {\n    offWindowEvent('online', onOnline)\n    offWindowEvent('offline', onOffline)\n  }\n}\n\nexport const preset = {\n  isOnline,\n  isVisible\n} as const\n\nexport const defaultConfigOptions: ProviderConfiguration = {\n  initFocus,\n  initReconnect\n}\n"
  },
  {
    "path": "src/_internal/utils/with-middleware.ts",
    "content": "import { normalize } from './normalize-args'\n\nimport type {\n  Key,\n  Fetcher,\n  Middleware,\n  SWRConfiguration,\n  SWRHook\n} from '../types'\n\n// Create a custom hook with a middleware\nexport const withMiddleware = (\n  useSWR: SWRHook,\n  middleware: Middleware\n): SWRHook => {\n  return <Data = any, Error = any>(\n    ...args:\n      | [Key]\n      | [Key, Fetcher<Data> | null]\n      | [Key, SWRConfiguration | undefined]\n      | [Key, Fetcher<Data> | null, SWRConfiguration | undefined]\n  ) => {\n    const [key, fn, config] = normalize(args)\n    const uses = (config.use || []).concat(middleware)\n    return useSWR<Data, Error>(key, fn, { ...config, use: uses })\n  }\n}\n"
  },
  {
    "path": "src/immutable/index.ts",
    "content": "import type { Middleware } from '../index'\nimport useSWR from '../index'\nimport { withMiddleware } from '../_internal'\n\nexport const immutable: Middleware = useSWRNext => (key, fetcher, config) => {\n  // Always override all revalidate options.\n  config.revalidateOnFocus = false\n  config.revalidateIfStale = false\n  config.revalidateOnReconnect = false\n  config.refreshInterval = 0\n  return useSWRNext(key, fetcher, config)\n}\n\nconst useSWRImmutable = withMiddleware(useSWR, immutable)\n\nexport default useSWRImmutable\n"
  },
  {
    "path": "src/index/config.ts",
    "content": "'use client'\n\n// TODO: fix SWRConfig re-use issue with bundler\nimport { SWRConfig as S } from '../_internal'\nexport const SWRConfig = S\n"
  },
  {
    "path": "src/index/index.react-server.ts",
    "content": "export { unstable_serialize } from './serialize'\nexport { SWRConfig } from './config'\n"
  },
  {
    "path": "src/index/index.ts",
    "content": "// useSWR\nimport useSWR from './use-swr'\nexport default useSWR\n// Core APIs\nexport { SWRConfig } from './use-swr'\nexport { unstable_serialize } from './serialize'\nexport { useSWRConfig } from '../_internal'\nexport { mutate } from '../_internal'\nexport { preload } from '../_internal'\n\n// Config\n\n// eslint-disable-next-line @typescript-eslint/no-empty-object-type\nexport interface SWRGlobalConfig {\n  // suspense: true\n}\n\n// Types\nexport type {\n  SWRConfiguration,\n  Revalidator,\n  RevalidatorOptions,\n  Key,\n  KeyLoader,\n  KeyedMutator,\n  SWRHook,\n  SWRResponse,\n  Cache,\n  BareFetcher,\n  Fetcher,\n  MutatorCallback,\n  MutatorOptions,\n  Middleware,\n  Arguments,\n  State,\n  ScopedMutator\n} from '../_internal'\n"
  },
  {
    "path": "src/index/serialize.ts",
    "content": "import type { Key } from '../_internal'\nimport { serialize } from '../_internal/utils/serialize'\n\nexport const unstable_serialize = (key: Key) => serialize(key)[0]\n"
  },
  {
    "path": "src/index/use-swr.ts",
    "content": "/// <reference types=\"react/experimental\" />\nimport React, { useCallback, useRef, useDebugValue, useMemo } from 'react'\nimport { useSyncExternalStore } from 'use-sync-external-store/shim'\n\nimport {\n  defaultConfig,\n  IS_REACT_LEGACY,\n  IS_SERVER,\n  rAF,\n  useIsomorphicLayoutEffect,\n  SWRGlobalState,\n  serialize,\n  isUndefined,\n  UNDEFINED,\n  OBJECT,\n  isFunction,\n  createCacheHelper,\n  SWRConfig as ConfigProvider,\n  withArgs,\n  subscribeCallback,\n  getTimestamp,\n  internalMutate,\n  revalidateEvents,\n  mergeObjects,\n  isPromiseLike,\n  noop\n} from '../_internal'\nimport type {\n  State,\n  Fetcher,\n  Key,\n  SWRResponse,\n  RevalidatorOptions,\n  FullConfiguration,\n  SWRConfiguration,\n  SWRHook,\n  RevalidateEvent,\n  StateDependencies,\n  GlobalState\n} from '../_internal'\n\nconst use =\n  React.use ||\n  // This extra generic is to avoid TypeScript mixing up the generic and JSX sytax\n  // and emitting an error.\n  // We assume that this is only for the `use(thenable)` case, not `use(context)`.\n  // https://github.com/facebook/react/blob/aed00dacfb79d17c53218404c52b1c7aa59c4a89/packages/react-server/src/ReactFizzThenable.js#L45\n  (<T, _>(\n    thenable: Promise<T> & {\n      status?: 'pending' | 'fulfilled' | 'rejected'\n      value?: T\n      reason?: unknown\n    }\n  ): T => {\n    switch (thenable.status) {\n      case 'pending':\n        throw thenable\n      case 'fulfilled':\n        return thenable.value as T\n      case 'rejected':\n        throw thenable.reason\n      default:\n        thenable.status = 'pending'\n        thenable.then(\n          v => {\n            thenable.status = 'fulfilled'\n            thenable.value = v\n          },\n          e => {\n            thenable.status = 'rejected'\n            thenable.reason = e\n          }\n        )\n        throw thenable\n    }\n  })\n\nconst WITH_DEDUPE = { dedupe: true }\n\ntype DefinitelyTruthy<T> = false extends T\n  ? never\n  : 0 extends T\n  ? never\n  : '' extends T\n  ? never\n  : null extends T\n  ? never\n  : undefined extends T\n  ? never\n  : T\n\nconst resolvedUndef = Promise.resolve(UNDEFINED)\nconst sub = () => noop\n/**\n * The core implementation of the useSWR hook.\n *\n * This is the main handler function that implements all SWR functionality including\n * data fetching, caching, revalidation, error handling, and state management.\n * It manages the complete lifecycle of SWR requests from initialization through\n * cleanup.\n *\n * Key responsibilities:\n * - Key serialization and normalization\n * - Cache state management and synchronization\n * - Automatic and manual revalidation\n * - Error handling and retry logic\n * - Suspense integration\n * - Loading state management\n * - Effect cleanup and memory management\n *\n * @template Data - The type of data returned by the fetcher\n * @template Error - The type of error that can be thrown\n *\n * @param _key - The SWR key (string, array, object, function, or falsy)\n * @param fetcher - The fetcher function to retrieve data, or null to disable fetching\n * @param config - Complete SWR configuration object with both public and internal options\n *\n * @returns SWRResponse object containing data, error, mutate function, and loading states\n *\n * @internal This is the internal implementation. Use `useSWR` instead.\n */\nexport const useSWRHandler = <Data = any, Error = any>(\n  _key: Key,\n  fetcher: Fetcher<Data> | null,\n  config: FullConfiguration & SWRConfiguration<Data, Error>\n) => {\n  const {\n    cache,\n    compare,\n    suspense,\n    fallbackData,\n    revalidateOnMount,\n    revalidateIfStale,\n    refreshInterval,\n    refreshWhenHidden,\n    refreshWhenOffline,\n    keepPreviousData,\n    strictServerPrefetchWarning\n  } = config\n\n  const [EVENT_REVALIDATORS, MUTATION, FETCH, PRELOAD] = SWRGlobalState.get(\n    cache\n  ) as GlobalState\n\n  // `key` is the identifier of the SWR internal state,\n  // `fnArg` is the argument/arguments parsed from the key, which will be passed\n  // to the fetcher.\n  // All of them are derived from `_key`.\n  const [key, fnArg] = serialize(_key)\n\n  // If it's the initial render of this hook.\n  const initialMountedRef = useRef(false)\n\n  // If the hook is unmounted already. This will be used to prevent some effects\n  // to be called after unmounting.\n  const unmountedRef = useRef(false)\n\n  // Refs to keep the key and config.\n  const keyRef = useRef(key)\n  const fetcherRef = useRef(fetcher)\n  const configRef = useRef(config)\n  const getConfig = () => configRef.current\n  const isActive = () => getConfig().isVisible() && getConfig().isOnline()\n\n  const [getCache, setCache, subscribeCache, getInitialCache] =\n    createCacheHelper<\n      Data,\n      State<Data, any> & {\n        // The original key arguments.\n        _k?: Key\n      }\n    >(cache, key)\n\n  const stateDependencies = useRef<StateDependencies>({}).current\n\n  // Resolve the fallback data from either the inline option, or the global provider.\n  // If it's a promise, we simply let React suspend and resolve it for us.\n  const fallback = isUndefined(fallbackData)\n    ? isUndefined(config.fallback)\n      ? UNDEFINED\n      : config.fallback[key]\n    : fallbackData\n\n  const isEqual = (prev: State<Data, any>, current: State<Data, any>) => {\n    for (const _ in stateDependencies) {\n      const t = _ as keyof StateDependencies\n      if (t === 'data') {\n        if (!compare(prev[t], current[t])) {\n          if (!isUndefined(prev[t])) {\n            return false\n          }\n          if (!compare(returnedData, current[t])) {\n            return false\n          }\n        }\n      } else {\n        if (current[t] !== prev[t]) {\n          return false\n        }\n      }\n    }\n    return true\n  }\n  const isInitialMount = !initialMountedRef.current\n  const getSnapshot = useMemo(() => {\n    const cachedData = getCache()\n    const initialData = getInitialCache()\n    const getSelectedCache = (state: ReturnType<typeof getCache>) => {\n      // We only select the needed fields from the state.\n      const snapshot = mergeObjects(state)\n      delete snapshot._k\n\n      const shouldStartRequest = (() => {\n        if (!key) return false\n        if (!fetcher) return false\n        // If it's paused, we skip revalidation.\n        if (getConfig().isPaused()) return false\n        // If `revalidateOnMount` is set, we take the value directly.\n        if (isInitialMount && !isUndefined(revalidateOnMount))\n          return revalidateOnMount\n        const data = !isUndefined(fallback) ? fallback : snapshot.data\n        if (suspense) return isUndefined(data) || revalidateIfStale\n        return isUndefined(data) || revalidateIfStale\n      })()\n\n      if (!shouldStartRequest) {\n        return snapshot\n      }\n\n      return {\n        isValidating: true,\n        isLoading: true,\n        ...snapshot\n      }\n    }\n\n    const clientSnapshot = getSelectedCache(cachedData)\n    const serverSnapshot =\n      cachedData === initialData\n        ? clientSnapshot\n        : getSelectedCache(initialData)\n    // To make sure that we are returning the same object reference to avoid\n    // unnecessary re-renders, we keep the previous snapshot and use deep\n    // comparison to check if we need to return a new one.\n    let memorizedSnapshot = clientSnapshot\n\n    return [\n      () => {\n        const newSnapshot = getSelectedCache(getCache())\n        const compareResult = isEqual(newSnapshot, memorizedSnapshot)\n\n        if (compareResult) {\n          // Mentally, we should always return the `memorizedSnapshot` here\n          // as there's no change between the new and old snapshots.\n          // However, since the `isEqual` function only compares selected fields,\n          // the values of the unselected fields might be changed. That's\n          // simply because we didn't track them.\n          // To support the case in https://github.com/vercel/swr/pull/2576,\n          // we need to update these fields in the `memorizedSnapshot` too\n          // with direct mutations to ensure the snapshot is always up-to-date\n          // even for the unselected fields, but only trigger re-renders when\n          // the selected fields are changed.\n          memorizedSnapshot.data = newSnapshot.data\n          memorizedSnapshot.isLoading = newSnapshot.isLoading\n          memorizedSnapshot.isValidating = newSnapshot.isValidating\n          memorizedSnapshot.error = newSnapshot.error\n          return memorizedSnapshot\n        } else {\n          memorizedSnapshot = newSnapshot\n          return newSnapshot\n        }\n      },\n      () => serverSnapshot\n    ]\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, [cache, key])\n\n  // Get the current state that SWR should return.\n  const cached = useSyncExternalStore(\n    useCallback(\n      (callback: () => void) =>\n        subscribeCache(\n          key,\n          (current: State<Data, any>, prev: State<Data, any>) => {\n            if (!isEqual(prev, current)) callback()\n          }\n        ),\n      // eslint-disable-next-line react-hooks/exhaustive-deps\n      [cache, key]\n    ),\n    getSnapshot[0],\n    getSnapshot[1]\n  )\n\n  const hasRevalidator =\n    EVENT_REVALIDATORS[key] && EVENT_REVALIDATORS[key].length > 0\n\n  const cachedData = cached.data\n\n  const data = isUndefined(cachedData)\n    ? fallback && isPromiseLike(fallback)\n      ? use(fallback)\n      : fallback\n    : cachedData\n  const error = cached.error\n\n  // Use a ref to store previously returned data. Use the initial data as its initial value.\n  const laggyDataRef = useRef(data)\n\n  const returnedData = keepPreviousData\n    ? isUndefined(cachedData)\n      ? // checking undefined to avoid null being fallback as well\n        isUndefined(laggyDataRef.current)\n        ? data\n        : laggyDataRef.current\n      : cachedData\n    : data\n\n  const hasKeyButNoData = key && isUndefined(data)\n  const hydrationRef = useRef<boolean | null>(null)\n  // Note: the conditionally hook call is fine because the environment\n  // `IS_SERVER` never changes.\n  // @ts-expect-error -- use hydrationRef directly\n  const _ =\n    !IS_SERVER &&\n    // getServerSnapshot is only called during hydration\n    // eslint-disable-next-line react-hooks/rules-of-hooks\n    useSyncExternalStore(\n      sub,\n      () => {\n        hydrationRef.current = false\n        return hydrationRef\n      },\n      () => {\n        hydrationRef.current = true\n        return hydrationRef\n      }\n    )\n  const isHydration = hydrationRef.current\n  // During the initial SSR render, warn if the key has no data pre-fetched via:\n  // - fallback data\n  // - preload calls\n  // - initial data from the cache provider\n  // We only warn once for each key during Hydration.\n  if (\n    strictServerPrefetchWarning &&\n    isHydration &&\n    !suspense &&\n    hasKeyButNoData\n  ) {\n    console.warn(\n      `Missing pre-initiated data for serialized key \"${key}\" during server-side rendering. Data fetching should be initiated on the server and provided to SWR via fallback data. You can set \"strictServerPrefetchWarning: false\" to disable this warning.`\n    )\n  }\n\n  // Resolve the default validating state:\n  // If it's able to validate, and it should revalidate when mount, this will be true.\n  // - Suspense mode and there's stale data for the initial render.\n  // - Not suspense mode and there is no fallback data and `revalidateIfStale` is enabled.\n  // - `revalidateIfStale` is enabled but `data` is not defined.\n  const shouldDoInitialRevalidation = (() => {\n    if (!key || !fetcher) return false\n    // If it's paused, we skip revalidation.\n    if (getConfig().isPaused()) return false\n    // if a key already has revalidators and also has error, we should not trigger revalidation\n    if (hasRevalidator && !isUndefined(error)) return false\n    // If `revalidateOnMount` is set, we take the value directly.\n    if (isInitialMount && !isUndefined(revalidateOnMount))\n      return revalidateOnMount\n    // Under suspense mode, it will always fetch on render if there is no\n    // stale data so no need to revalidate immediately mount it again.\n    // If data exists, only revalidate if `revalidateIfStale` is true.\n    if (suspense) return isUndefined(data) ? false : revalidateIfStale\n    // If there is no stale data, we need to revalidate when mount;\n    // If `revalidateIfStale` is set to true, we will always revalidate.\n    return isUndefined(data) || revalidateIfStale\n  })()\n\n  const defaultValidatingState = isInitialMount && shouldDoInitialRevalidation\n\n  const isValidating = isUndefined(cached.isValidating)\n    ? defaultValidatingState\n    : cached.isValidating\n  const isLoading = isUndefined(cached.isLoading)\n    ? defaultValidatingState\n    : cached.isLoading\n\n  // The revalidation function is a carefully crafted wrapper of the original\n  // `fetcher`, to correctly handle the many edge cases.\n  const revalidate = useCallback(\n    async (revalidateOpts?: RevalidatorOptions): Promise<boolean> => {\n      const currentFetcher = fetcherRef.current\n\n      if (\n        !key ||\n        !currentFetcher ||\n        unmountedRef.current ||\n        getConfig().isPaused()\n      ) {\n        return false\n      }\n\n      let newData: Data\n      let startAt: number\n      let loading = true\n      const opts = revalidateOpts || {}\n\n      // If there is no ongoing concurrent request, or `dedupe` is not set, a\n      // new request should be initiated.\n      const shouldStartNewRequest = !FETCH[key] || !opts.dedupe\n\n      /*\n         For React 17\n         Do unmount check for calls:\n         If key has changed during the revalidation, or the component has been\n         unmounted, old dispatch and old event callbacks should not take any\n         effect\n\n        For React 18\n        only check if key has changed\n        https://github.com/reactwg/react-18/discussions/82\n      */\n      const callbackSafeguard = () => {\n        if (IS_REACT_LEGACY) {\n          return (\n            !unmountedRef.current &&\n            key === keyRef.current &&\n            initialMountedRef.current\n          )\n        }\n        return key === keyRef.current\n      }\n\n      // The final state object when the request finishes.\n      const finalState: State<Data, Error> = {\n        isValidating: false,\n        isLoading: false\n      }\n      const finishRequestAndUpdateState = () => {\n        setCache(finalState)\n      }\n      const cleanupState = () => {\n        // Check if it's still the same request before deleting it.\n        const requestInfo = FETCH[key]\n        if (requestInfo && requestInfo[1] === startAt) {\n          delete FETCH[key]\n        }\n      }\n\n      // Start fetching. Change the `isValidating` state, update the cache.\n      const initialState: State<Data, Error> = { isValidating: true }\n      // It is in the `isLoading` state, if and only if there is no cached data.\n      // This bypasses fallback data and laggy data.\n      if (isUndefined(getCache().data)) {\n        initialState.isLoading = true\n      }\n      try {\n        if (shouldStartNewRequest) {\n          setCache(initialState)\n          // If no cache is being rendered currently (it shows a blank page),\n          // we trigger the loading slow event.\n          if (config.loadingTimeout && isUndefined(getCache().data)) {\n            setTimeout(() => {\n              if (loading && callbackSafeguard()) {\n                getConfig().onLoadingSlow(key, config)\n              }\n            }, config.loadingTimeout)\n          }\n\n          // Start the request and save the timestamp.\n          // Key must be truthy if entering here.\n          FETCH[key] = [\n            currentFetcher(fnArg as DefinitelyTruthy<Key>),\n            getTimestamp()\n          ]\n        }\n\n        // Wait until the ongoing request is done. Deduplication is also\n        // considered here.\n        ;[newData, startAt] = FETCH[key]\n        newData = await newData\n\n        if (shouldStartNewRequest) {\n          // If the request isn't interrupted, clean it up after the\n          // deduplication interval.\n          setTimeout(cleanupState, config.dedupingInterval)\n        }\n\n        // If there're other ongoing request(s), started after the current one,\n        // we need to ignore the current one to avoid possible race conditions:\n        //   req1------------------>res1        (current one)\n        //        req2---------------->res2\n        // the request that fired later will always be kept.\n        // The timestamp maybe be `undefined` or a number\n        if (!FETCH[key] || FETCH[key][1] !== startAt) {\n          if (shouldStartNewRequest) {\n            if (callbackSafeguard()) {\n              getConfig().onDiscarded(key)\n            }\n          }\n          return false\n        }\n\n        // Clear error.\n        finalState.error = UNDEFINED\n\n        // If there're other mutations(s), that overlapped with the current revalidation:\n        // case 1:\n        //   req------------------>res\n        //       mutate------>end\n        // case 2:\n        //         req------------>res\n        //   mutate------>end\n        // case 3:\n        //   req------------------>res\n        //       mutate-------...---------->\n        // we have to ignore the revalidation result (res) because it's no longer fresh.\n        // meanwhile, a new revalidation should be triggered when the mutation ends.\n        const mutationInfo = MUTATION[key]\n        if (\n          !isUndefined(mutationInfo) &&\n          // case 1\n          (startAt <= mutationInfo[0] ||\n            // case 2\n            startAt <= mutationInfo[1] ||\n            // case 3\n            mutationInfo[1] === 0)\n        ) {\n          finishRequestAndUpdateState()\n          if (shouldStartNewRequest) {\n            if (callbackSafeguard()) {\n              getConfig().onDiscarded(key)\n            }\n          }\n          return false\n        }\n        // Deep compare with the latest state to avoid extra re-renders.\n        // For local state, compare and assign.\n        const cacheData = getCache().data\n\n        // Since the compare fn could be custom fn\n        // cacheData might be different from newData even when compare fn returns True\n        finalState.data = compare(cacheData, newData) ? cacheData : newData\n\n        // Trigger the successful callback if it's the original request.\n        if (shouldStartNewRequest) {\n          if (callbackSafeguard()) {\n            getConfig().onSuccess(newData, key, config)\n          }\n        }\n      } catch (err: any) {\n        cleanupState()\n\n        const currentConfig = getConfig()\n        const { shouldRetryOnError } = currentConfig\n\n        // Not paused, we continue handling the error. Otherwise, discard it.\n        if (!currentConfig.isPaused()) {\n          // Get a new error, don't use deep comparison for errors.\n          finalState.error = err as Error\n\n          // Error event and retry logic. Only for the actual request, not\n          // deduped ones.\n          if (shouldStartNewRequest && callbackSafeguard()) {\n            currentConfig.onError(err, key, currentConfig)\n            if (\n              shouldRetryOnError === true ||\n              (isFunction(shouldRetryOnError) &&\n                shouldRetryOnError(err as Error))\n            ) {\n              if (\n                !getConfig().revalidateOnFocus ||\n                !getConfig().revalidateOnReconnect ||\n                isActive()\n              ) {\n                // If it's inactive, stop. It will auto-revalidate when\n                // refocusing or reconnecting.\n                // When retrying, deduplication is always enabled.\n                currentConfig.onErrorRetry(\n                  err,\n                  key,\n                  currentConfig,\n                  _opts => {\n                    const revalidators = EVENT_REVALIDATORS[key]\n                    if (revalidators && revalidators[0]) {\n                      revalidators[0](\n                        revalidateEvents.ERROR_REVALIDATE_EVENT,\n                        _opts\n                      )\n                    }\n                  },\n                  {\n                    retryCount: (opts.retryCount || 0) + 1,\n                    dedupe: true\n                  }\n                )\n              }\n            }\n          }\n        }\n      }\n\n      // Mark loading as stopped.\n      loading = false\n\n      // Update the current hook's state.\n      finishRequestAndUpdateState()\n\n      return true\n    },\n    // `setState` is immutable, and `eventsCallback`, `fnArg`, and\n    // `keyValidating` are depending on `key`, so we can exclude them from\n    // the deps array.\n    //\n    // FIXME:\n    // `fn` and `config` might be changed during the lifecycle,\n    // but they might be changed every render like this.\n    // `useSWR('key', () => fetch('/api/'), { suspense: true })`\n    // So we omit the values from the deps array\n    // even though it might cause unexpected behaviors.\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n    [key, cache]\n  )\n\n  // Similar to the global mutate but bound to the current cache and key.\n  // `cache` isn't allowed to change during the lifecycle.\n  const boundMutate: SWRResponse<Data, Error>['mutate'] = useCallback(\n    // Use callback to make sure `keyRef.current` returns latest result every time\n    (...args: any[]) => {\n      return internalMutate(cache, keyRef.current, ...args)\n    },\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n    []\n  )\n\n  // The logic for updating refs.\n  useIsomorphicLayoutEffect(() => {\n    fetcherRef.current = fetcher\n    configRef.current = config\n    // Handle laggy data updates. If there's cached data of the current key,\n    // it'll be the correct reference.\n    if (!isUndefined(cachedData)) {\n      laggyDataRef.current = cachedData\n    }\n  })\n\n  // After mounted or key changed.\n  useIsomorphicLayoutEffect(() => {\n    if (!key) return\n\n    const softRevalidate = revalidate.bind(UNDEFINED, WITH_DEDUPE)\n\n    let nextFocusRevalidatedAt = 0\n\n    if (getConfig().revalidateOnFocus) {\n      const initNow = Date.now()\n      nextFocusRevalidatedAt = initNow + getConfig().focusThrottleInterval\n    }\n\n    // Expose revalidators to global event listeners. So we can trigger\n    // revalidation from the outside.\n    const onRevalidate = (\n      type: RevalidateEvent,\n      opts: {\n        retryCount?: number\n        dedupe?: boolean\n      } = {}\n    ) => {\n      if (type == revalidateEvents.FOCUS_EVENT) {\n        const now = Date.now()\n        if (\n          getConfig().revalidateOnFocus &&\n          now > nextFocusRevalidatedAt &&\n          isActive()\n        ) {\n          nextFocusRevalidatedAt = now + getConfig().focusThrottleInterval\n          softRevalidate()\n        }\n      } else if (type == revalidateEvents.RECONNECT_EVENT) {\n        if (getConfig().revalidateOnReconnect && isActive()) {\n          softRevalidate()\n        }\n      } else if (type == revalidateEvents.MUTATE_EVENT) {\n        return revalidate()\n      } else if (type == revalidateEvents.ERROR_REVALIDATE_EVENT) {\n        return revalidate(opts)\n      }\n      return\n    }\n\n    const unsubEvents = subscribeCallback(key, EVENT_REVALIDATORS, onRevalidate)\n\n    // Mark the component as mounted and update corresponding refs.\n    unmountedRef.current = false\n    keyRef.current = key\n    initialMountedRef.current = true\n\n    // Keep the original key in the cache.\n    setCache({ _k: fnArg })\n\n    // Trigger a revalidation\n    if (shouldDoInitialRevalidation) {\n      // Performance optimization: if a request is already in progress for this key,\n      // skip the revalidation to avoid redundant work\n      if (!FETCH[key]) {\n        if (isUndefined(data) || IS_SERVER) {\n          // Revalidate immediately.\n          softRevalidate()\n        } else {\n          // Delay the revalidate if we have data to return so we won't block\n          // rendering.\n          rAF(softRevalidate)\n        }\n      }\n    }\n\n    return () => {\n      // Mark it as unmounted.\n      unmountedRef.current = true\n\n      unsubEvents()\n    }\n  }, [key])\n\n  // Polling\n  useIsomorphicLayoutEffect(() => {\n    let timer: any\n\n    function next() {\n      // Use the passed interval\n      // ...or invoke the function with the updated data to get the interval\n      const interval = isFunction(refreshInterval)\n        ? refreshInterval(getCache().data)\n        : refreshInterval\n\n      // We only start the next interval if `refreshInterval` is not 0, and:\n      // - `force` is true, which is the start of polling\n      // - or `timer` is not 0, which means the effect wasn't canceled\n      if (interval && timer !== -1) {\n        timer = setTimeout(execute, interval)\n      }\n    }\n\n    function execute() {\n      // Check if it's OK to execute:\n      // Only revalidate when the page is visible, online, and not errored.\n      if (\n        !getCache().error &&\n        (refreshWhenHidden || getConfig().isVisible()) &&\n        (refreshWhenOffline || getConfig().isOnline())\n      ) {\n        revalidate(WITH_DEDUPE).then(next)\n      } else {\n        // Schedule the next interval to check again.\n        next()\n      }\n    }\n\n    next()\n\n    return () => {\n      if (timer) {\n        clearTimeout(timer)\n        timer = -1\n      }\n    }\n  }, [refreshInterval, refreshWhenHidden, refreshWhenOffline, key])\n\n  // Display debug info in React DevTools.\n  useDebugValue(returnedData)\n\n  // In Suspense mode, we can't return the empty `data` state.\n  // If there is an `error`, the `error` needs to be thrown to the error boundary.\n  // If there is no `error`, the `revalidation` promise needs to be thrown to\n  // the suspense boundary.\n  if (suspense) {\n    // SWR should throw when trying to use Suspense on the server with React 18,\n    // without providing any fallback data. This causes hydration errors. See:\n    // https://github.com/vercel/swr/issues/1832\n    if (!IS_REACT_LEGACY && IS_SERVER && hasKeyButNoData) {\n      throw new Error('Fallback data is required when using Suspense in SSR.')\n    }\n\n    // Always update fetcher and config refs even with the Suspense mode.\n    if (hasKeyButNoData) {\n      fetcherRef.current = fetcher\n      configRef.current = config\n      unmountedRef.current = false\n    }\n\n    const req = PRELOAD[key]\n\n    const mutateReq =\n      !isUndefined(req) && hasKeyButNoData ? boundMutate(req) : resolvedUndef\n    use(mutateReq)\n\n    if (!isUndefined(error) && hasKeyButNoData) {\n      throw error\n    }\n    const revalidation = hasKeyButNoData\n      ? revalidate(WITH_DEDUPE)\n      : resolvedUndef\n    if (!isUndefined(returnedData) && hasKeyButNoData) {\n      // @ts-ignore modify react promise status\n      revalidation.status = 'fulfilled'\n      // @ts-ignore modify react promise value\n      revalidation.value = true\n    }\n    use(revalidation)\n  }\n\n  const swrResponse: SWRResponse<Data, Error> = {\n    mutate: boundMutate,\n    get data() {\n      stateDependencies.data = true\n      return returnedData\n    },\n    get error() {\n      stateDependencies.error = true\n      return error\n    },\n    get isValidating() {\n      stateDependencies.isValidating = true\n      return isValidating\n    },\n    get isLoading() {\n      stateDependencies.isLoading = true\n      return isLoading\n    }\n  }\n  return swrResponse\n}\n\nexport const SWRConfig = OBJECT.defineProperty(ConfigProvider, 'defaultValue', {\n  value: defaultConfig\n}) as typeof ConfigProvider & {\n  defaultValue: FullConfiguration\n}\n\nexport { unstable_serialize } from './serialize'\n\n/**\n * A hook to fetch data.\n *\n * @see {@link https://swr.vercel.app}\n *\n * @example\n * ```jsx\n * import useSWR from 'swr'\n * function Profile() {\n *   const { data, error, isLoading } = useSWR('/api/user', fetcher)\n *   if (error) return <div>failed to load</div>\n *   if (isLoading) return <div>loading...</div>\n *   return <div>hello {data.name}!</div>\n * }\n * ```\n */\nconst useSWR = withArgs<SWRHook>(useSWRHandler)\n\nexport default useSWR\n"
  },
  {
    "path": "src/infinite/index.react-server.ts",
    "content": "export { unstable_serialize } from './serialize'\n"
  },
  {
    "path": "src/infinite/index.ts",
    "content": "// We have to several type castings here because `useSWRInfinite` is a special\n// hook where `key` and return type are not like the normal `useSWR` types.\n\nimport { useRef, useCallback } from 'react'\nimport type { SWRConfig } from '../index'\nimport useSWR from '../index'\nimport {\n  isUndefined,\n  isFunction,\n  UNDEFINED,\n  createCacheHelper,\n  useIsomorphicLayoutEffect,\n  serialize,\n  withMiddleware,\n  INFINITE_PREFIX,\n  SWRGlobalState,\n  cache as defaultCache\n} from '../_internal'\nimport type {\n  BareFetcher,\n  SWRHook,\n  MutatorCallback,\n  Middleware,\n  GlobalState\n} from '../_internal'\nimport type {\n  SWRInfiniteConfiguration,\n  SWRInfiniteResponse,\n  SWRInfiniteHook,\n  SWRInfiniteKeyLoader,\n  SWRInfiniteFetcher,\n  SWRInfiniteCacheValue,\n  SWRInfiniteCompareFn,\n  SWRInfiniteKeyedMutator,\n  SWRInfiniteMutatorOptions\n} from './types'\nimport { useSyncExternalStore } from 'use-sync-external-store/shim'\nimport { getFirstPageKey } from './serialize'\n\nconst EMPTY_PROMISE = Promise.resolve() as Promise<undefined>\n\nexport { unstable_serialize } from './serialize'\n\nexport const infinite = (<Data, Error>(useSWRNext: SWRHook) =>\n  (\n    getKey: SWRInfiniteKeyLoader,\n    fn: BareFetcher<Data> | null,\n    config: Omit<typeof SWRConfig.defaultValue, 'fetcher'> &\n      Omit<SWRInfiniteConfiguration<Data, Error>, 'fetcher'>\n  ) => {\n    const didMountRef = useRef<boolean>(false)\n    const {\n      cache,\n      initialSize = 1,\n      revalidateAll = false,\n      persistSize = false,\n      revalidateFirstPage = true,\n      revalidateOnMount = false,\n      parallel = false\n    } = config\n    const [, , , PRELOAD] = SWRGlobalState.get(defaultCache) as GlobalState\n\n    // The serialized key of the first page. This key will be used to store\n    // metadata of this SWR infinite hook.\n    let infiniteKey: string | undefined\n    try {\n      infiniteKey = getFirstPageKey(getKey)\n      if (infiniteKey) infiniteKey = INFINITE_PREFIX + infiniteKey\n    } catch (err) {\n      // Not ready yet.\n    }\n\n    const [get, set, subscribeCache] = createCacheHelper<\n      Data,\n      SWRInfiniteCacheValue<Data, any>\n    >(cache, infiniteKey)\n\n    const getSnapshot = useCallback(() => {\n      const size = isUndefined(get()._l) ? initialSize : get()._l\n      return size\n      // eslint-disable-next-line react-hooks/exhaustive-deps\n    }, [cache, infiniteKey, initialSize])\n    useSyncExternalStore(\n      useCallback(\n        (callback: () => void) => {\n          if (infiniteKey)\n            return subscribeCache(infiniteKey, () => {\n              callback()\n            })\n          return () => {}\n        },\n        // eslint-disable-next-line react-hooks/exhaustive-deps\n        [cache, infiniteKey]\n      ),\n      getSnapshot,\n      getSnapshot\n    )\n\n    const resolvePageSize = useCallback((): number => {\n      const cachedPageSize = get()._l\n      return isUndefined(cachedPageSize) ? initialSize : cachedPageSize\n\n      // `cache` isn't allowed to change during the lifecycle\n      // eslint-disable-next-line react-hooks/exhaustive-deps\n    }, [infiniteKey, initialSize])\n    // keep the last page size to restore it with the persistSize option\n    const lastPageSizeRef = useRef<number>(resolvePageSize())\n\n    // When the page key changes, we reset the page size if it's not persisted\n    useIsomorphicLayoutEffect(() => {\n      if (!didMountRef.current) {\n        didMountRef.current = true\n        return\n      }\n\n      if (infiniteKey) {\n        // If the key has been changed, we keep the current page size if persistSize is enabled\n        // Otherwise, we reset the page size to cached pageSize\n        set({ _l: persistSize ? lastPageSizeRef.current : resolvePageSize() })\n      }\n\n      // `initialSize` isn't allowed to change during the lifecycle\n    }, [infiniteKey, cache])\n\n    // Needs to check didMountRef during mounting, not in the fetcher\n    const shouldRevalidateOnMount = revalidateOnMount && !didMountRef.current\n\n    // Actual SWR hook to load all pages in one fetcher.\n    const swr = useSWRNext(\n      infiniteKey,\n      async key => {\n        // get the revalidate context\n        const forceRevalidateAll = get()._i\n        const shouldRevalidatePage = get()._r\n        set({ _r: UNDEFINED })\n\n        // return an array of page data\n        const data: Data[] = []\n\n        const pageSize = resolvePageSize()\n        const [getCache] = createCacheHelper<\n          Data,\n          SWRInfiniteCacheValue<Data[], any>\n        >(cache, key)\n        const cacheData = getCache().data\n\n        const revalidators = []\n\n        let previousPageData = null\n        for (let i = 0; i < pageSize; ++i) {\n          const [pageKey, pageArg] = serialize(\n            getKey(i, parallel ? null : previousPageData)\n          )\n\n          if (!pageKey) {\n            // `pageKey` is falsy, stop fetching new pages.\n            break\n          }\n\n          const [getSWRCache, setSWRCache] = createCacheHelper<\n            Data,\n            SWRInfiniteCacheValue<Data, any>\n          >(cache, pageKey)\n\n          // Get the cached page data.\n          let pageData = getSWRCache().data as Data\n          // should fetch (or revalidate) if:\n          // - `revalidateAll` is enabled\n          // - `mutate()` called\n          // - the cache is missing\n          // - it's the first page and it's not the initial render\n          // - `revalidateOnMount` is enabled and it's on mount\n          // - cache for that page has changed\n          const shouldFetchPage =\n            revalidateAll ||\n            forceRevalidateAll ||\n            isUndefined(pageData) ||\n            (revalidateFirstPage && !i && !isUndefined(cacheData)) ||\n            shouldRevalidateOnMount ||\n            (cacheData &&\n              !isUndefined(cacheData[i]) &&\n              !config.compare(cacheData[i], pageData))\n          if (\n            fn &&\n            (typeof shouldRevalidatePage === 'function'\n              ? shouldRevalidatePage(pageData, pageArg)\n              : shouldFetchPage)\n          ) {\n            const revalidate = async () => {\n              const hasPreloadedRequest = pageKey in PRELOAD\n              if (!hasPreloadedRequest) {\n                pageData = await fn(pageArg)\n              } else {\n                const req = PRELOAD[pageKey]\n                // delete the preload cache key before resolving it\n                // in case there's an error\n                delete PRELOAD[pageKey]\n                // get the page data from the preload cache\n                pageData = await req\n              }\n              setSWRCache({ data: pageData, _k: pageArg })\n              data[i] = pageData\n            }\n            if (parallel) {\n              revalidators.push(revalidate)\n            } else {\n              await revalidate()\n            }\n          } else {\n            data[i] = pageData\n          }\n          if (!parallel) {\n            previousPageData = pageData\n          }\n        }\n\n        // flush all revalidateions in parallel\n        if (parallel) {\n          await Promise.all(revalidators.map(r => r()))\n        }\n\n        // once we executed the data fetching based on the context, clear the context\n        set({ _i: UNDEFINED })\n\n        // return the data\n        return data\n      },\n      config\n    )\n\n    const mutate = useCallback(\n      // eslint-disable-next-line func-names\n      function <T = Data[]>(\n        data?:\n          | undefined\n          | Data[]\n          | Promise<Data[] | undefined>\n          | MutatorCallback<Data[]>,\n        opts?: undefined | boolean | SWRInfiniteMutatorOptions<Data[], T>\n      ) {\n        // When passing as a boolean, it's explicitly used to disable/enable\n        // revalidation.\n        const options =\n          typeof opts === 'boolean' ? { revalidate: opts } : opts || {}\n\n        // Default to true.\n        const shouldRevalidate = options.revalidate !== false\n\n        // It is possible that the key is still falsy.\n        if (!infiniteKey) return EMPTY_PROMISE\n        if (shouldRevalidate) {\n          if (!isUndefined(data)) {\n            // We only revalidate the pages that are changed\n            set({ _i: false, _r: options.revalidate })\n          } else {\n            // Calling `mutate()`, we revalidate all pages\n            set({ _i: true, _r: options.revalidate })\n          }\n        }\n\n        return arguments.length\n          ? swr.mutate(data, { ...options, revalidate: shouldRevalidate })\n          : swr.mutate()\n      },\n      // swr.mutate is always the same reference\n      // eslint-disable-next-line react-hooks/exhaustive-deps\n      [infiniteKey, cache]\n    )\n    // Extend the SWR API\n\n    const setSize = useCallback(\n      (arg: number | ((size: number) => number)) => {\n        // It is possible that the key is still falsy.\n        if (!infiniteKey) return EMPTY_PROMISE\n        const [, changeSize] = createCacheHelper<\n          Data,\n          SWRInfiniteCacheValue<Data, any>\n        >(cache, infiniteKey)\n        let size\n        if (isFunction(arg)) {\n          size = arg(resolvePageSize())\n        } else if (typeof arg == 'number') {\n          size = arg\n        }\n        if (typeof size != 'number') return EMPTY_PROMISE\n\n        changeSize({ _l: size })\n        lastPageSizeRef.current = size\n\n        // Calculate the page data after the size change.\n        const data: Data[] = []\n        const [getInfiniteCache] = createCacheHelper<\n          Data,\n          SWRInfiniteCacheValue<Data[], any>\n        >(cache, infiniteKey)\n        let previousPageData = null\n        for (let i = 0; i < size; ++i) {\n          const [pageKey] = serialize(getKey(i, previousPageData))\n          const [getCache] = createCacheHelper<\n            Data,\n            SWRInfiniteCacheValue<Data, any>\n          >(cache, pageKey)\n          // Get the cached page data.\n          const pageData = pageKey ? getCache().data : UNDEFINED\n\n          // Call `mutate` with infinte cache data if we can't get it from the page cache.\n          if (isUndefined(pageData)) {\n            return mutate(getInfiniteCache().data)\n          }\n\n          data.push(pageData)\n          previousPageData = pageData\n        }\n        return mutate(data)\n      },\n      // exclude getKey from the dependencies, which isn't allowed to change during the lifecycle\n      // eslint-disable-next-line react-hooks/exhaustive-deps\n      [infiniteKey, cache, mutate, resolvePageSize]\n    )\n\n    // Use getter functions to avoid unnecessary re-renders caused by triggering\n    // all the getters of the returned swr object.\n    return {\n      size: resolvePageSize(),\n      setSize,\n      mutate,\n      get data() {\n        return swr.data\n      },\n      get error() {\n        return swr.error\n      },\n      get isValidating() {\n        return swr.isValidating\n      },\n      get isLoading() {\n        return swr.isLoading\n      }\n    }\n  }) as unknown as Middleware\n\nconst useSWRInfinite = withMiddleware(useSWR, infinite) as SWRInfiniteHook\n\nexport default useSWRInfinite\n\nexport {\n  SWRInfiniteConfiguration,\n  SWRInfiniteResponse,\n  SWRInfiniteHook,\n  SWRInfiniteKeyLoader,\n  SWRInfiniteFetcher,\n  SWRInfiniteCompareFn,\n  SWRInfiniteKeyedMutator,\n  SWRInfiniteMutatorOptions\n}\n"
  },
  {
    "path": "src/infinite/serialize.ts",
    "content": "import type { SWRInfiniteKeyLoader } from './types'\nimport { serialize } from '../_internal/utils/serialize'\nimport { INFINITE_PREFIX } from '../_internal/constants'\n\nexport const getFirstPageKey = (getKey: SWRInfiniteKeyLoader) => {\n  return serialize(getKey ? getKey(0, null) : null)[0]\n}\n\nexport const unstable_serialize = (getKey: SWRInfiniteKeyLoader) => {\n  return INFINITE_PREFIX + getFirstPageKey(getKey)\n}\n"
  },
  {
    "path": "src/infinite/types.ts",
    "content": "import type {\n  SWRConfiguration,\n  SWRResponse,\n  Arguments,\n  BareFetcher,\n  State,\n  StrictTupleKey,\n  MutatorOptions,\n  MutatorCallback\n} from '../_internal'\n\ntype FetcherResponse<Data = unknown> = Data | Promise<Data>\n\nexport type SWRInfiniteFetcher<\n  Data = any,\n  KeyLoader extends SWRInfiniteKeyLoader = SWRInfiniteKeyLoader\n> = KeyLoader extends (...args: any[]) => any\n  ? ReturnType<KeyLoader> extends infer T | null | false | undefined\n    ? (args: T) => FetcherResponse<Data>\n    : never\n  : never\n\nexport type SWRInfiniteKeyLoader<\n  Data = any,\n  Args extends Arguments = Arguments\n> = (index: number, previousPageData: Data | null) => Args\n\nexport interface SWRInfiniteCompareFn<Data = any> {\n  (a: Data | undefined, b: Data | undefined): boolean\n  (a: Data[] | undefined, b: Data[] | undefined): boolean\n}\nexport interface SWRInfiniteConfiguration<\n  Data = any,\n  Error = any,\n  Fn extends SWRInfiniteFetcher<Data> = BareFetcher<Data>\n> extends Omit<SWRConfiguration<Data[], Error>, 'compare'> {\n  initialSize?: number\n  revalidateAll?: boolean\n  persistSize?: boolean\n  revalidateFirstPage?: boolean\n  parallel?: boolean\n  fetcher?: Fn\n  compare?: SWRInfiniteCompareFn<Data>\n}\n\ninterface SWRInfiniteRevalidateFn<Data = any> {\n  (data: Data, key: Arguments): boolean\n}\n\nexport type SWRInfiniteKeyedMutator<Data> = <MutationData = Data>(\n  data?: Data | Promise<Data | undefined> | MutatorCallback<Data>,\n  opts?: boolean | SWRInfiniteMutatorOptions<Data, MutationData>\n) => Promise<Data | MutationData | undefined>\n\nexport interface SWRInfiniteMutatorOptions<Data = any, MutationData = Data>\n  extends Omit<MutatorOptions<Data, MutationData>, 'revalidate'> {\n  revalidate?:\n    | boolean\n    | SWRInfiniteRevalidateFn<Data extends unknown[] ? Data[number] : never>\n}\n\nexport interface SWRInfiniteResponse<Data = any, Error = any>\n  extends Omit<SWRResponse<Data[], Error>, 'mutate'> {\n  size: number\n  setSize: (\n    size: number | ((_size: number) => number)\n  ) => Promise<Data[] | undefined>\n  mutate: SWRInfiniteKeyedMutator<Data[]>\n}\n\nexport interface SWRInfiniteHook {\n  <\n    Data = any,\n    Error = any,\n    KeyLoader extends SWRInfiniteKeyLoader = (\n      index: number,\n      previousPageData: Data | null\n    ) => StrictTupleKey\n  >(\n    getKey: KeyLoader\n  ): SWRInfiniteResponse<Data, Error>\n  <\n    Data = any,\n    Error = any,\n    KeyLoader extends SWRInfiniteKeyLoader = (\n      index: number,\n      previousPageData: Data | null\n    ) => StrictTupleKey\n  >(\n    getKey: KeyLoader,\n    fetcher: SWRInfiniteFetcher<Data, KeyLoader> | null\n  ): SWRInfiniteResponse<Data, Error>\n  <\n    Data = any,\n    Error = any,\n    KeyLoader extends SWRInfiniteKeyLoader = (\n      index: number,\n      previousPageData: Data | null\n    ) => StrictTupleKey\n  >(\n    getKey: KeyLoader,\n    config:\n      | SWRInfiniteConfiguration<\n          Data,\n          Error,\n          SWRInfiniteFetcher<Data, KeyLoader>\n        >\n      | undefined\n  ): SWRInfiniteResponse<Data, Error>\n  <\n    Data = any,\n    Error = any,\n    KeyLoader extends SWRInfiniteKeyLoader = (\n      index: number,\n      previousPageData: Data | null\n    ) => StrictTupleKey\n  >(\n    getKey: KeyLoader,\n    fetcher: SWRInfiniteFetcher<Data, KeyLoader> | null,\n    config:\n      | SWRInfiniteConfiguration<\n          Data,\n          Error,\n          SWRInfiniteFetcher<Data, KeyLoader>\n        >\n      | undefined\n  ): SWRInfiniteResponse<Data, Error>\n  <Data = any, Error = any>(getKey: SWRInfiniteKeyLoader): SWRInfiniteResponse<\n    Data,\n    Error\n  >\n  <Data = any, Error = any>(\n    getKey: SWRInfiniteKeyLoader,\n    fetcher: BareFetcher<Data> | null\n  ): SWRInfiniteResponse<Data, Error>\n  <Data = any, Error = any>(\n    getKey: SWRInfiniteKeyLoader,\n    config: SWRInfiniteConfiguration<Data, Error, BareFetcher<Data>> | undefined\n  ): SWRInfiniteResponse<Data, Error>\n  <Data = any, Error = any>(\n    getKey: SWRInfiniteKeyLoader,\n    fetcher: BareFetcher<Data> | null,\n    config: SWRInfiniteConfiguration<Data, Error, BareFetcher<Data>> | undefined\n  ): SWRInfiniteResponse<Data, Error>\n}\n\nexport interface SWRInfiniteCacheValue<Data = any, Error = any>\n  extends State<Data, Error> {\n  // We use cache to pass extra info (context) to fetcher so it can be globally\n  // shared. The key of the context data is based on the first-page key.\n  _i?: boolean\n  // Page size is also cached to share the page data between hooks with the\n  // same key.\n  _l?: number\n  _k?: Arguments\n  _r?: boolean | SWRInfiniteRevalidateFn\n}\n"
  },
  {
    "path": "src/mutation/index.ts",
    "content": "import { useCallback, useRef } from 'react'\nimport useSWR, { useSWRConfig } from '../index'\nimport type { Middleware, Key } from '../_internal'\nimport { useStateWithDeps, startTransition } from './state'\nimport {\n  serialize,\n  withMiddleware,\n  useIsomorphicLayoutEffect,\n  UNDEFINED,\n  getTimestamp,\n  mergeObjects\n} from '../_internal'\nimport type {\n  SWRMutationConfiguration,\n  SWRMutationResponse,\n  SWRMutationHook,\n  MutationFetcher,\n  TriggerWithArgs,\n  TriggerWithoutArgs,\n  TriggerWithOptionsArgs\n} from './types'\n\nconst mutation = (<Data, Error>() =>\n  (\n    key: Key,\n    fetcher: MutationFetcher<Data>,\n    config: SWRMutationConfiguration<Data, Error> = {}\n  ) => {\n    const { mutate } = useSWRConfig()\n    const keyRef = useRef(key)\n    const fetcherRef = useRef(fetcher)\n    const configRef = useRef(config)\n    // Ditch all mutation results that happened earlier than this timestamp.\n    const ditchMutationsUntilRef = useRef(0)\n\n    const [stateRef, stateDependencies, setState] = useStateWithDeps<{\n      data: Data | undefined\n      error: Error | undefined\n      isMutating: boolean\n    }>({\n      data: UNDEFINED,\n      error: UNDEFINED,\n      isMutating: false\n    })\n\n    const currentState = stateRef.current\n\n    const trigger = useCallback(\n      async (arg: any, opts?: SWRMutationConfiguration<Data, Error>) => {\n        const [serializedKey, resolvedKey] = serialize(keyRef.current)\n\n        if (!fetcherRef.current) {\n          throw new Error('Can’t trigger the mutation: missing fetcher.')\n        }\n        if (!serializedKey) {\n          throw new Error('Can’t trigger the mutation: missing key.')\n        }\n\n        // Disable cache population by default.\n        const options = mergeObjects(\n          mergeObjects(\n            { populateCache: false, throwOnError: true },\n            configRef.current\n          ),\n          opts\n        )\n\n        // Trigger a mutation, and also track the timestamp. Any mutation that happened\n        // earlier this timestamp should be ignored.\n        const mutationStartedAt = getTimestamp()\n\n        ditchMutationsUntilRef.current = mutationStartedAt\n\n        setState({ isMutating: true })\n\n        try {\n          const data = await mutate<Data>(\n            serializedKey,\n            (fetcherRef.current as any)(resolvedKey, { arg }),\n            // We must throw the error here so we can catch and update the states.\n            mergeObjects(options, { throwOnError: true })\n          )\n\n          // If it's reset after the mutation, we don't broadcast any state change.\n          if (ditchMutationsUntilRef.current <= mutationStartedAt) {\n            startTransition(() =>\n              setState({ data, isMutating: false, error: undefined })\n            )\n            options.onSuccess?.(data as Data, serializedKey, options)\n          }\n          return data\n        } catch (error) {\n          // If it's reset after the mutation, we don't broadcast any state change\n          // or throw because it's discarded.\n          if (ditchMutationsUntilRef.current <= mutationStartedAt) {\n            startTransition(() =>\n              setState({ error: error as Error, isMutating: false })\n            )\n            options.onError?.(error as Error, serializedKey, options)\n            if (options.throwOnError) {\n              throw error as Error\n            }\n          }\n        }\n      },\n      // eslint-disable-next-line react-hooks/exhaustive-deps\n      []\n    )\n\n    const reset = useCallback(() => {\n      ditchMutationsUntilRef.current = getTimestamp()\n      setState({ data: UNDEFINED, error: UNDEFINED, isMutating: false })\n      // eslint-disable-next-line react-hooks/exhaustive-deps\n    }, [])\n\n    useIsomorphicLayoutEffect(() => {\n      keyRef.current = key\n      fetcherRef.current = fetcher\n      configRef.current = config\n    })\n\n    // We don't return `mutate` here as it can be pretty confusing (e.g. people\n    // calling `mutate` but they actually mean `trigger`).\n    // And also, `mutate` relies on the useSWR hook to exist too.\n    return {\n      trigger,\n      reset,\n      get data() {\n        stateDependencies.data = true\n        return currentState.data\n      },\n      get error() {\n        stateDependencies.error = true\n        return currentState.error\n      },\n      get isMutating() {\n        stateDependencies.isMutating = true\n        return currentState.isMutating\n      }\n    }\n  }) as unknown as Middleware\n\n/**\n * A hook to define and manually trigger remote mutations like POST, PUT, DELETE and PATCH use cases.\n *\n * @link https://swr.vercel.app/docs/mutation\n * @example\n * ```jsx\n * import useSWRMutation from 'swr/mutation'\n *\n * const {\n *   data,\n *   error,\n *   trigger,\n *   reset,\n *   isMutating\n * } = useSWRMutation(key, fetcher, options?)\n * ```\n */\nconst useSWRMutation = withMiddleware(\n  useSWR,\n  mutation\n) as unknown as SWRMutationHook\n\nexport default useSWRMutation\n\nexport {\n  SWRMutationConfiguration,\n  SWRMutationResponse,\n  SWRMutationHook,\n  MutationFetcher,\n  TriggerWithArgs,\n  TriggerWithoutArgs,\n  TriggerWithOptionsArgs\n}\n"
  },
  {
    "path": "src/mutation/state.ts",
    "content": "import type { MutableRefObject, TransitionFunction } from 'react'\nimport React, { useRef, useCallback, useState } from 'react'\nimport { useIsomorphicLayoutEffect, IS_REACT_LEGACY } from '../_internal'\n\nexport const startTransition: (scope: TransitionFunction) => void =\n  IS_REACT_LEGACY\n    ? cb => {\n        cb()\n      }\n    : React.startTransition\n\n/**\n * An implementation of state with dependency-tracking.\n * @param initialState - The initial state object.\n */\nexport const useStateWithDeps = <S = Record<string, any>>(\n  initialState: S\n): [\n  MutableRefObject<S>,\n  Record<keyof S, boolean>,\n  (payload: Partial<S>) => void\n] => {\n  const [, rerender] = useState<Record<string, unknown>>({})\n  const unmountedRef = useRef(false)\n  const stateRef = useRef<S>(initialState)\n\n  // If a state property (data, error, or isValidating) is accessed by the render\n  // function, we mark the property as a dependency so if it is updated again\n  // in the future, we trigger a rerender.\n  // This is also known as dependency-tracking.\n  const stateDependenciesRef = useRef<Record<keyof S, boolean>>({\n    data: false,\n    error: false,\n    isValidating: false\n  } as Record<keyof S, boolean>)\n\n  /**\n   * Updates state and triggers re-render if necessary.\n   * @param payload To change stateRef, pass the values explicitly to setState:\n   * @example\n   * ```js\n   * setState({\n   *   isValidating: false\n   *   data: newData // set data to newData\n   *   error: undefined // set error to undefined\n   * })\n   *\n   * setState({\n   *   isValidating: false\n   *   data: undefined // set data to undefined\n   *   error: err // set error to err\n   * })\n   * ```\n   */\n  const setState = useCallback((payload: Partial<S>) => {\n    let shouldRerender = false\n\n    const currentState = stateRef.current\n    for (const key in payload) {\n      if (Object.prototype.hasOwnProperty.call(payload, key)) {\n        const k = key as keyof S\n\n        // If the property has changed, update the state and mark rerender as\n        // needed.\n        if (currentState[k] !== payload[k]) {\n          currentState[k] = payload[k]!\n\n          // If the property is accessed by the component, a rerender should be\n          // triggered.\n          if (stateDependenciesRef.current[k]) {\n            shouldRerender = true\n          }\n        }\n      }\n    }\n\n    if (shouldRerender && !unmountedRef.current) {\n      rerender({})\n    }\n  }, [])\n\n  useIsomorphicLayoutEffect(() => {\n    unmountedRef.current = false\n    return () => {\n      unmountedRef.current = true\n    }\n  })\n\n  return [stateRef, stateDependenciesRef.current, setState]\n}\n"
  },
  {
    "path": "src/mutation/types.ts",
    "content": "import type { SWRResponse, Key, Arguments } from '../index'\n\ntype FetcherResponse<Data> = Data | Promise<Data>\n\ntype FetcherOptions<ExtraArg = unknown> = Readonly<{\n  arg: ExtraArg\n}>\n\nexport type MutationFetcher<\n  Data = unknown,\n  SWRKey extends Key = Key,\n  ExtraArg = unknown\n> = SWRKey extends () => infer Arg | null | undefined | false\n  ? (key: Arg, options: FetcherOptions<ExtraArg>) => FetcherResponse<Data>\n  : SWRKey extends null | undefined | false\n  ? never\n  : SWRKey extends infer Arg\n  ? (key: Arg, options: FetcherOptions<ExtraArg>) => FetcherResponse<Data>\n  : never\n\nexport type SWRMutationConfiguration<\n  Data,\n  Error,\n  SWRMutationKey extends Key = Key,\n  ExtraArg = any,\n  SWRData = any\n> = {\n  revalidate?: boolean | ((data: Data, key: Arguments) => boolean)\n  populateCache?:\n    | boolean\n    | ((result: Data, currentData: SWRData | undefined) => SWRData)\n  optimisticData?: SWRData | ((currentData?: SWRData) => SWRData)\n  rollbackOnError?: boolean | ((error: unknown) => boolean)\n  fetcher?: MutationFetcher<Data, SWRMutationKey, ExtraArg>\n  onSuccess?: (\n    data: Data,\n    key: string,\n    config: Readonly<\n      SWRMutationConfiguration<Data, Error, SWRMutationKey, ExtraArg, SWRData>\n    >\n  ) => void\n  onError?: (\n    err: Error,\n    key: string,\n    config: Readonly<\n      SWRMutationConfiguration<Data, Error, SWRMutationKey, ExtraArg, SWRData>\n    >\n  ) => void\n}\n\ntype RemoveUndefined<T> = T extends undefined ? never : T\ntype IsUndefinedIncluded<T> = undefined extends T ? true : false\nexport interface TriggerWithArgs<\n  Data = any,\n  Error = any,\n  SWRMutationKey extends Key = Key,\n  ExtraArg = never\n> {\n  <SWRData = Data>(\n    extraArgument: ExtraArg,\n    options?: SWRMutationConfiguration<\n      Data,\n      Error,\n      SWRMutationKey,\n      ExtraArg,\n      SWRData\n    >\n  ): Promise<Data>\n  <SWRData = Data>(\n    extraArgument: ExtraArg,\n    options?: SWRMutationConfiguration<\n      Data,\n      Error,\n      SWRMutationKey,\n      ExtraArg,\n      SWRData\n    > & { throwOnError: true }\n  ): Promise<RemoveUndefined<Data>>\n  <SWRData = Data>(\n    extraArgument: ExtraArg,\n    options?: SWRMutationConfiguration<\n      Data,\n      Error,\n      SWRMutationKey,\n      ExtraArg,\n      SWRData\n    > & { throwOnError: false }\n  ): Promise<Data | undefined>\n}\n\nexport interface TriggerWithOptionsArgs<\n  Data = any,\n  Error = any,\n  SWRMutationKey extends Key = Key,\n  ExtraArg = never\n> {\n  <SWRData = Data>(\n    extraArgument?: ExtraArg,\n    options?: SWRMutationConfiguration<\n      Data,\n      Error,\n      SWRMutationKey,\n      ExtraArg,\n      SWRData\n    >\n  ): Promise<Data>\n  <SWRData = Data>(\n    extraArgument?: ExtraArg,\n    options?: SWRMutationConfiguration<\n      Data,\n      Error,\n      SWRMutationKey,\n      ExtraArg,\n      SWRData\n    > & { throwOnError: true }\n  ): Promise<RemoveUndefined<Data>>\n  <SWRData = Data>(\n    extraArgument?: ExtraArg,\n    options?: SWRMutationConfiguration<\n      Data,\n      Error,\n      SWRMutationKey,\n      ExtraArg,\n      SWRData\n    > & { throwOnError: false }\n  ): Promise<Data | undefined>\n}\n\nexport interface TriggerWithoutArgs<\n  Data = any,\n  Error = any,\n  SWRMutationKey extends Key = Key,\n  ExtraArg = never\n> {\n  <SWRData = Data>(\n    extraArgument?: null,\n    options?: SWRMutationConfiguration<\n      Data,\n      Error,\n      SWRMutationKey,\n      ExtraArg,\n      SWRData\n    >\n  ): Promise<Data>\n  <SWRData = Data>(\n    extraArgument?: null,\n    options?: SWRMutationConfiguration<\n      Data,\n      Error,\n      SWRMutationKey,\n      ExtraArg,\n      SWRData\n    > & { throwOnError: true }\n  ): Promise<RemoveUndefined<Data>>\n  <SWRData = Data>(\n    extraArgument?: null,\n    options?: SWRMutationConfiguration<\n      Data,\n      Error,\n      SWRMutationKey,\n      ExtraArg,\n      SWRData\n    > & { throwOnError: false }\n  ): Promise<Data>\n}\n\nexport interface SWRMutationResponse<\n  Data = any,\n  Error = any,\n  SWRMutationKey extends Key = Key,\n  ExtraArg = never\n> extends Pick<SWRResponse<Data, Error>, 'data' | 'error'> {\n  /**\n   * Indicates if the mutation is in progress.\n   */\n  isMutating: boolean\n  /**\n   * Function to trigger the mutation. You can also pass an extra argument to\n   * the fetcher, and override the options for the mutation hook.\n   */\n  trigger: [ExtraArg] extends [never]\n    ? TriggerWithoutArgs<Data, Error, SWRMutationKey, ExtraArg>\n    : IsUndefinedIncluded<ExtraArg> extends true\n    ? TriggerWithOptionsArgs<Data, Error, SWRMutationKey, ExtraArg>\n    : TriggerWithArgs<Data, Error, SWRMutationKey, ExtraArg>\n  /**\n   * Function to reset the mutation state (`data`, `error`, and `isMutating`).\n   */\n  reset: () => void\n}\n\nexport interface SWRMutationHook {\n  <\n    Data = any,\n    Error = any,\n    SWRMutationKey extends Key = Key,\n    ExtraArg = never,\n    SWRData = Data\n  >(\n    /**\n     * The key of the resource that will be mutated. It should be the same key\n     * used in the `useSWR` hook so SWR can handle revalidation and race\n     * conditions for that resource.\n     */\n    key: SWRMutationKey,\n    /**\n     * The function to trigger the mutation that accepts the key, extra argument\n     * and options. For example:\n     *\n     * ```jsx\n     * (api, data) => fetch(api, {\n     *   method: 'POST',\n     *   body: JSON.stringify(data)\n     * })\n     * ```\n     */\n    fetcher: MutationFetcher<Data, SWRMutationKey, ExtraArg>,\n    /**\n     * Extra options for the mutation hook.\n     */\n    options?: SWRMutationConfiguration<\n      Data,\n      Error,\n      SWRMutationKey,\n      ExtraArg,\n      SWRData\n    > & { throwOnError?: boolean }\n  ): SWRMutationResponse<Data, Error, SWRMutationKey, ExtraArg>\n  <\n    Data = any,\n    Error = any,\n    SWRMutationKey extends Key = Key,\n    ExtraArg = never,\n    SWRData = Data\n  >(\n    /**\n     * The key of the resource that will be mutated. It should be the same key\n     * used in the `useSWR` hook so SWR can handle revalidation and race\n     * conditions for that resource.\n     */\n    key: SWRMutationKey,\n    /**\n     * The function to trigger the mutation that accepts the key, extra argument\n     * and options. For example:\n     *\n     * ```jsx\n     * (api, data) => fetch(api, {\n     *   method: 'POST',\n     *   body: JSON.stringify(data)\n     * })\n     * ```\n     */\n    fetcher: MutationFetcher<Data, SWRMutationKey, ExtraArg>,\n    /**\n     * Extra options for the mutation hook.\n     */\n    options?: SWRMutationConfiguration<\n      Data,\n      Error,\n      SWRMutationKey,\n      ExtraArg,\n      SWRData\n    > & { throwOnError: false }\n  ): SWRMutationResponse<Data | undefined, Error, SWRMutationKey, ExtraArg>\n  <\n    Data = any,\n    Error = any,\n    SWRMutationKey extends Key = Key,\n    ExtraArg = never,\n    SWRData = Data\n  >(\n    /**\n     * The key of the resource that will be mutated. It should be the same key\n     * used in the `useSWR` hook so SWR can handle revalidation and race\n     * conditions for that resource.\n     */\n    key: SWRMutationKey,\n    /**\n     * The function to trigger the mutation that accepts the key, extra argument\n     * and options. For example:\n     *\n     * ```jsx\n     * (api, data) => fetch(api, {\n     *   method: 'POST',\n     *   body: JSON.stringify(data)\n     * })\n     * ```\n     */\n    fetcher: MutationFetcher<Data, SWRMutationKey, ExtraArg>,\n    /**\n     * Extra options for the mutation hook.\n     */\n    options?: SWRMutationConfiguration<\n      Data,\n      Error,\n      SWRMutationKey,\n      ExtraArg,\n      SWRData\n    > & { throwOnError: true }\n  ): SWRMutationResponse<Data, Error, SWRMutationKey, ExtraArg>\n}\n"
  },
  {
    "path": "src/subscription/index.ts",
    "content": "import type {\n  Key,\n  SWRHook,\n  Middleware,\n  SWRConfiguration,\n  SWRConfig\n} from '../index'\nimport type {\n  SWRSubscriptionOptions,\n  SWRSubscription,\n  SWRSubscriptionResponse,\n  SWRSubscriptionHook\n} from './types'\nimport useSWR from '../index'\nimport {\n  withMiddleware,\n  serialize,\n  useIsomorphicLayoutEffect,\n  createCacheHelper\n} from '../_internal'\n\n// [subscription count, disposer]\ntype SubscriptionStates = [Map<string, number>, Map<string, () => void>]\nconst subscriptionStorage = new WeakMap<object, SubscriptionStates>()\n\nconst SUBSCRIPTION_PREFIX = '$sub$'\n\nexport const subscription = (<Data = any, Error = any>(useSWRNext: SWRHook) =>\n  (\n    _key: Key,\n    subscribe: SWRSubscription<any, Data, Error>,\n    config: SWRConfiguration & typeof SWRConfig.defaultValue\n  ): SWRSubscriptionResponse<Data, Error> => {\n    const [key, args] = serialize(_key)\n\n    // Prefix the key to avoid conflicts with other SWR resources.\n    const subscriptionKey = key ? SUBSCRIPTION_PREFIX + key : undefined\n    const swr = useSWRNext(subscriptionKey, null, config)\n\n    const { cache } = config\n\n    // Ensure that the subscription state is scoped by the cache boundary, so\n    // you can have multiple SWR zones with subscriptions having the same key.\n    if (!subscriptionStorage.has(cache)) {\n      subscriptionStorage.set(cache, [\n        new Map<string, number>(),\n        new Map<string, () => void>()\n      ])\n    }\n\n    const [subscriptions, disposers] = subscriptionStorage.get(cache)!\n\n    useIsomorphicLayoutEffect(() => {\n      if (!subscriptionKey) return\n\n      const [, set] = createCacheHelper<Data>(cache, subscriptionKey)\n      const refCount = subscriptions.get(subscriptionKey) || 0\n\n      const next: SWRSubscriptionOptions<Data, Error>['next'] = (\n        error,\n        data\n      ) => {\n        if (error !== null && typeof error !== 'undefined') {\n          set({ error })\n        } else {\n          set({ error: undefined })\n          swr.mutate(data, false)\n        }\n      }\n\n      // Increment the ref count.\n      subscriptions.set(subscriptionKey, refCount + 1)\n\n      if (!refCount) {\n        const dispose = subscribe(args, { next })\n        if (typeof dispose !== 'function') {\n          throw new Error(\n            'The `subscribe` function must return a function to unsubscribe.'\n          )\n        }\n        disposers.set(subscriptionKey, dispose)\n      }\n\n      return () => {\n        const count = subscriptions.get(subscriptionKey)! - 1\n\n        subscriptions.set(subscriptionKey, count)\n\n        // Dispose if it's the last one.\n        if (!count) {\n          const dispose = disposers.get(subscriptionKey)\n          dispose?.()\n        }\n      }\n    }, [subscriptionKey])\n\n    return {\n      get data() {\n        return swr.data\n      },\n      get error() {\n        return swr.error\n      }\n    }\n  }) as unknown as Middleware\n\n/**\n * A hook to subscribe a SWR resource to an external data source for continuous updates.\n * @experimental This API is experimental and might change in the future.\n * @example\n * ```jsx\n * import useSWRSubscription from 'swr/subscription'\n *\n * const { data, error } = useSWRSubscription(key, (key, { next }) => {\n *   const unsubscribe = dataSource.subscribe(key, (err, data) => {\n *     next(err, data)\n *   })\n *   return unsubscribe\n * })\n * ```\n */\nconst useSWRSubscription = withMiddleware(\n  useSWR,\n  subscription\n) as SWRSubscriptionHook\n\nexport default useSWRSubscription\n\nexport type {\n  SWRSubscription,\n  SWRSubscriptionOptions,\n  SWRSubscriptionResponse,\n  SWRSubscriptionHook\n}\n"
  },
  {
    "path": "src/subscription/types.ts",
    "content": "import type { Key, SWRConfiguration, MutatorCallback } from '../index'\n\nexport type SWRSubscriptionOptions<Data = any, Error = any> = {\n  next: (err?: Error | null, data?: Data | MutatorCallback<Data>) => void\n}\n\nexport type SWRSubscription<\n  SWRSubKey extends Key = Key,\n  Data = any,\n  Error = any\n> = SWRSubKey extends () => infer Arg | null | undefined | false\n  ? (key: Arg, { next }: SWRSubscriptionOptions<Data, Error>) => void\n  : SWRSubKey extends null | undefined | false\n  ? never\n  : SWRSubKey extends infer Arg\n  ? (key: Arg, { next }: SWRSubscriptionOptions<Data, Error>) => void\n  : never\n\nexport type SWRSubscriptionResponse<Data = any, Error = any> = {\n  data?: Data\n  error?: Error\n}\n\nexport type SWRSubscriptionHook = <\n  Data = any,\n  Error = any,\n  SWRSubKey extends Key = Key\n>(\n  key: SWRSubKey,\n  subscribe: SWRSubscription<SWRSubKey, Data, Error>,\n  config?: SWRConfiguration\n) => SWRSubscriptionResponse<Data, Error>\n"
  },
  {
    "path": "subscription/package.json",
    "content": "{\n  \"main\": \"../dist/subscription/index.js\",\n  \"module\": \"../dist/subscription/index.mjs\",\n  \"types\": \"../dist/subscription/index.d.ts\",\n  \"private\": true\n}\n"
  },
  {
    "path": "test/jest-setup.ts",
    "content": "import '@testing-library/jest-dom'\n"
  },
  {
    "path": "test/tsconfig.json",
    "content": "{\n  \"extends\": \"../tsconfig.json\",\n  \"compilerOptions\": {\n    \"noEmit\": true,\n    \"strict\": false,\n    \"jsx\": \"react-jsx\",\n    \"baseUrl\": \"..\",\n    \"paths\": {\n      \"swr\": [\"./core/src/index.ts\"],\n      \"swr/infinite\": [\"./infinite/src/index.ts\"],\n      \"swr/immutable\": [\"./immutable/src/index.ts\"],\n      \"swr/mutation\": [\"./mutation/src/index.ts\"],\n      \"swr/_internal\": [\"./_internal/src/index.ts\"],\n      \"swr/subscription\": [\"subscription/src/index.ts\"]\n    }\n  },\n  \"include\": [\".\", \"./jest-setup.ts\"],\n  \"exclude\": [\"./type\"]\n}\n"
  },
  {
    "path": "test/type/.eslintrc",
    "content": "{\n  \"extends\": \"../../.eslintrc\",\n  \"rules\": {\n    \"react-hooks/rules-of-hooks\": 0\n  }\n}"
  },
  {
    "path": "test/type/config.tsx",
    "content": "import type { Cache, SWRResponse } from 'swr'\nimport useSWR, { useSWRConfig, SWRConfig } from 'swr'\nimport { expectType } from './utils'\nimport type { FullConfiguration, SWRConfiguration } from 'swr/_internal'\nimport type { Equal } from '@type-challenges/utils'\n\nexport function useTestCache() {\n  expectType<Cache<any>>(useSWRConfig().cache)\n}\n\nexport function useTestCustomSWRConfig() {\n  const noNull = [\n    // @ts-expect-error\n    <SWRConfig key={'null'} value={null} />,\n    // @ts-expect-error\n    <SWRConfig key={'callback-return-null'} value={() => null} />\n  ]\n\n  return (\n    <>\n      {noNull}\n      <SWRConfig value={undefined} />\n      <SWRConfig value={() => ({})} />\n      <SWRConfig\n        value={{\n          fallback: {\n            '/api': 'fallback',\n            '/api2': Promise.resolve('fallback2')\n          }\n        }}\n      />\n\n      <SWRConfig\n        // @ts-expect-error\n        value={() => 0}\n      />\n    </>\n  )\n}\n\nexport function useTestFullConfiguration() {\n  type IData = { value: string }\n  type IError = { error: any }\n  type IConfig = FullConfiguration<IData, IError>\n\n  const config: IConfig = SWRConfig.defaultValue\n  expectType<IData | Promise<IData> | undefined>(config.fallbackData)\n}\n\nexport function testSWRResponseCachedDataTypes() {\n  type FilledData = SWRResponse<string, any, { suspense: true }>['data']\n  expectType<Equal<FilledData, string>>(true)\n\n  type FilledConditionalData = SWRResponse<\n    string | number,\n    any,\n    { suspense: true }\n  >['data']\n  expectType<Equal<FilledConditionalData, string | number>>(true)\n}\n\nexport function useTestSuspense() {\n  // Basic\n  const { data: data1 } = useSWR('/api', (k: string) => Promise.resolve(k), {\n    suspense: true\n  })\n  expectType<string>(data1)\n\n  // Basic(default fetcher)\n  const { data: data2 } = useSWR('/api', {\n    suspense: true\n  })\n  expectType<any>(data2)\n\n  // If a generic is explicitly passed we will lose type inference for the swr config\n  // because of partial inference limitations in typescript.\n  // https://github.com/microsoft/TypeScript/issues/26242\n  const { data: data3 } = useSWR<string>(\n    '/api',\n    (k: string) => Promise.resolve(k),\n    { suspense: true }\n  )\n  expectType<string | undefined>(data3)\n\n  const { data: data4 } = useSWR<string, any, { suspense: true }>(\n    '/api',\n    (k: string) => Promise.resolve(k),\n    { suspense: true }\n  )\n  expectType<string>(data4)\n}\n\nexport function useTestFallbackData() {\n  // Need to specify the type of Data returning from fetcher\n\n  // Basic\n  const fetcher1 = (k: string) => Promise.resolve(k)\n  const { data: data1 } = useSWR('/api', fetcher1, {\n    fallbackData: 'fallback'\n  })\n  expectType<string>(data1)\n\n  // Conditional\n  const fetcher2 = (k: string) =>\n    Promise.resolve(Math.random() > 0.5 ? k : Math.random() * 100)\n  const { data: data2 } = useSWR('/api', fetcher2, {\n    fallbackData: 'fallback'\n  })\n  expectType<string | number>(data2)\n\n  // Complex\n  const fetcher3 = (k: string) => Promise.resolve({ value: k })\n  const { data: data3 } = useSWR('/api', fetcher3, {\n    fallbackData: { value: 'fallback' }\n  })\n  expectType<{ value: string }>(data3)\n\n  // Does not need specific fetcher\n\n  // Basic(default fetcher)\n  const { data: data4 } = useSWR('/api', { fallbackData: 'fallback' })\n  expectType<any>(data4)\n\n  // If a generic is explicitly passed we will lose type inference for the swr config\n  // because of partial inference limitations in typescript.\n  // https://github.com/microsoft/TypeScript/issues/26242\n  const { data: data5 } = useSWR<string>(\n    '/api',\n    (k: string) => Promise.resolve(k),\n    { fallbackData: 'fallback' }\n  )\n  expectType<string | undefined>(data5)\n\n  const { data: data6 } = useSWR<string, any, { fallbackData: 'fallback' }>(\n    '/api',\n    (k: string) => Promise.resolve(k),\n    { fallbackData: 'fallback' }\n  )\n  expectType<string>(data6)\n\n  // Promise\n  const { data: data7 } = useSWR<\n    string,\n    any,\n    { fallbackData: Promise<'fallback'> }\n  >('/api', (k: string) => Promise.resolve(k), {\n    fallbackData: Promise.resolve('fallback')\n  })\n  expectType<string>(data7)\n\n  // Declare that the fallback is existing (i.e. provided by SWRConfig).\n  const { data: data8 } = useSWR<string, any, { fallbackData: string }>('/api')\n  const { data: data9 } = useSWR<string, any, { fallbackData: string }>(\n    '/api',\n    {}\n  )\n  expectType<string>(data8)\n  expectType<string>(data9)\n}\n\nexport function useTestConfigAsSWRConfiguration() {\n  const fetcher = (k: string) => Promise.resolve({ value: k })\n  const { data } = useSWR('/api', fetcher, {} as SWRConfiguration)\n  expectType<Equal<typeof data, { value: string } | undefined>>(true)\n}\n\nexport function useTestEmptyConfig() {\n  const fetcher = (k: string) => Promise.resolve({ value: k })\n  const { data, error, isLoading } = useSWR<{ value: string }, Error>(\n    '/api',\n    fetcher,\n    {}\n  )\n  expectType<Equal<typeof data, { value: string } | undefined>>(true)\n  expectType<Equal<typeof error, Error | undefined>>(true)\n  expectType<Equal<typeof isLoading, boolean>>(true)\n}\n\nexport function useTestFallbackDataConfig() {\n  const fetcher = (k: string) => Promise.resolve({ value: k })\n  const { data, isLoading } = useSWR('/api', fetcher, {\n    fallbackData: { value: 'fallback' }\n  })\n  expectType<Equal<typeof data, { value: string }>>(true)\n  expectType<Equal<typeof isLoading, boolean>>(true)\n}\n\nexport function useTestProviderConfig() {\n  const GlobalSetting = ({ children }: { children: React.ReactNode }) => {\n    return (\n      <SWRConfig\n        value={{\n          provider: () => new Map(),\n          isOnline() {\n            /* Customize the network state detector */\n            return true\n          },\n          isVisible() {\n            /* Customize the visibility state detector */\n            return true\n          },\n          initFocus(_callback) {\n            /* Register the listener with your state provider */\n          },\n          initReconnect(_callback) {\n            /* Register the listener with your state provider */\n          }\n        }}\n      >\n        {children}\n      </SWRConfig>\n    )\n  }\n  return (\n    <GlobalSetting>\n      <div />\n    </GlobalSetting>\n  )\n}\n"
  },
  {
    "path": "test/type/fetcher.ts",
    "content": "import useSWR from 'swr'\nimport useSWRInfinite from 'swr/infinite'\nimport { expectType, truthy } from './utils'\nimport type { Equal } from '@type-challenges/utils'\n\nexport function useDataErrorGeneric() {\n  useSWR<{ id: number }>('/api/', () => ({ id: 123 }))\n  useSWR<string, any>('/api/', (key: string) => key)\n  const fetcher = ({ url }: { url: string }) => url\n  useSWR({ url: '/api' }, fetcher)\n  useSWRInfinite<string[], any>(\n    (index, previousPageData) => {\n      expectType<Equal<number, typeof index>>(true)\n      expectType<Equal<string[] | null, typeof previousPageData>>(true)\n      return 'key'\n    },\n    key => key\n  )\n  useSWRInfinite<{ id: number }[], any>(\n    (index, previousPageData) => {\n      expectType<Equal<number, typeof index>>(true)\n      expectType<Equal<{ id: number }[] | null, typeof previousPageData>>(true)\n      return truthy() ? 'key' : null\n    },\n    key => key\n  )\n}\n\nexport function useString() {\n  useSWR('/api/user', key => {\n    expectType<Equal<'/api/user', typeof key>>(true)\n    return key\n  })\n\n  useSWR(truthy() ? '/api/user' : null, key => {\n    expectType<Equal<'/api/user', typeof key>>(true)\n    return key\n  })\n\n  useSWR(truthy() ? '/api/user' : false, key => {\n    expectType<Equal<'/api/user', typeof key>>(true)\n    return key\n  })\n}\n\nexport function useRecord() {\n  useSWR({ a: '1', b: { c: '3', d: 2 } }, key => {\n    expectType<Equal<{ a: string; b: { c: string; d: number } }, typeof key>>(\n      true\n    )\n    return key\n  })\n\n  useSWR(truthy() ? { a: '1', b: { c: '3', d: 2 } } : null, key => {\n    expectType<Equal<{ a: string; b: { c: string; d: number } }, typeof key>>(\n      true\n    )\n    return key\n  })\n\n  useSWR(truthy() ? { a: '1', b: { c: '3', d: 2 } } : false, key => {\n    expectType<Equal<{ a: string; b: { c: string; d: number } }, typeof key>>(\n      true\n    )\n    return key\n  })\n}\n\nexport function useTuple() {\n  useSWR([{ a: '1', b: { c: '3' } }, [1231, '888']], keys => {\n    expectType<\n      Equal<[{ a: string; b: { c: string } }, (string | number)[]], typeof keys>\n    >(true)\n    return keys\n  })\n  useSWR(truthy() ? [{ a: '1', b: { c: '3' } }, [1231, '888']] : null, keys => {\n    expectType<\n      Equal<[{ a: string; b: { c: string } }, (string | number)[]], typeof keys>\n    >(true)\n    return keys\n  })\n  useSWR(\n    truthy() ? [{ a: '1', b: { c: '3' } }, [1231, '888']] : false,\n    keys => {\n      expectType<\n        Equal<\n          [{ a: string; b: { c: string } }, (string | number)[]],\n          typeof keys\n        >\n      >(true)\n      return keys\n    }\n  )\n}\n\nexport function useReadonlyTuple() {\n  useSWR([{ a: '1', b: { c: '3' } }, [1231, '888']] as const, keys => {\n    expectType<\n      Equal<\n        readonly [\n          {\n            readonly a: '1'\n            readonly b: {\n              readonly c: '3'\n            }\n          },\n          readonly [1231, '888']\n        ],\n        typeof keys\n      >\n    >(true)\n    return keys\n  })\n  useSWR(\n    truthy() ? ([{ a: '1', b: { c: '3' } }, [1231, '888']] as const) : null,\n    keys => {\n      expectType<\n        Equal<\n          readonly [\n            {\n              readonly a: '1'\n              readonly b: {\n                readonly c: '3'\n              }\n            },\n            readonly [1231, '888']\n          ],\n          typeof keys\n        >\n      >(true)\n      return keys\n    }\n  )\n  useSWR(\n    truthy() ? ([{ a: '1', b: { c: '3' } }, [1231, '888']] as const) : false,\n    keys => {\n      expectType<\n        Equal<\n          readonly [\n            {\n              readonly a: '1'\n              readonly b: {\n                readonly c: '3'\n              }\n            },\n            readonly [1231, '888']\n          ],\n          typeof keys\n        >\n      >(true)\n      return keys\n    }\n  )\n}\n\nexport function useReturnString() {\n  useSWR(\n    () => '/api/user',\n    key => {\n      expectType<Equal<string, typeof key>>(true)\n      return key\n    }\n  )\n  useSWR(\n    () => (truthy() ? '/api/user' : null),\n    key => {\n      expectType<Equal<'/api/user', typeof key>>(true)\n      return key\n    }\n  )\n\n  useSWR(\n    () => (truthy() ? '/api/user' : false),\n    key => {\n      expectType<Equal<'/api/user', typeof key>>(true)\n      return key\n    }\n  )\n\n  useSWRInfinite(\n    (index, previousPageData: string) => {\n      return `${index}${previousPageData}`\n    },\n    key => {\n      expectType<Equal<string, typeof key>>(true)\n      return key\n    }\n  )\n\n  useSWRInfinite(\n    (index, previousPageData: string) => {\n      return truthy() ? `${index}${previousPageData}` : null\n    },\n    key => {\n      expectType<Equal<string, typeof key>>(true)\n      return key\n    }\n  )\n  useSWRInfinite(\n    (index, previousPageData: string) => {\n      return truthy() ? `${index}${previousPageData}` : false\n    },\n    key => {\n      expectType<Equal<string, typeof key>>(true)\n      return key\n    }\n  )\n}\n\nexport function useReturnRecord() {\n  useSWR(\n    () => ({ a: '1', b: { c: '3', d: 2 } }),\n    key => {\n      expectType<Equal<{ a: string; b: { c: string; d: number } }, typeof key>>(\n        true\n      )\n      return key\n    }\n  )\n  useSWR(\n    () => (truthy() ? { a: '1', b: { c: '3', d: 2 } } : null),\n    key => {\n      expectType<Equal<{ a: string; b: { c: string; d: number } }, typeof key>>(\n        true\n      )\n      return key\n    }\n  )\n\n  useSWR(\n    () => (truthy() ? { a: '1', b: { c: '3', d: 2 } } : false),\n    key => {\n      expectType<Equal<{ a: string; b: { c: string; d: number } }, typeof key>>(\n        true\n      )\n      return key\n    }\n  )\n\n  useSWRInfinite(\n    index => ({\n      index,\n      endPoint: '/api'\n    }),\n    key => {\n      expectType<Equal<{ index: number; endPoint: string }, typeof key>>(true)\n      return [key]\n    }\n  )\n\n  useSWRInfinite(\n    index =>\n      truthy()\n        ? {\n            index,\n            endPoint: '/api'\n          }\n        : null,\n    key => {\n      expectType<Equal<{ index: number; endPoint: string }, typeof key>>(true)\n      return [key]\n    }\n  )\n\n  useSWRInfinite(\n    index =>\n      truthy()\n        ? {\n            index,\n            endPoint: '/api'\n          }\n        : false,\n    key => {\n      expectType<Equal<{ index: number; endPoint: string }, typeof key>>(true)\n      return [key]\n    }\n  )\n}\n\nexport function useReturnTuple() {\n  useSWR(\n    () => [{ a: '1', b: { c: '3' } }, [1231, '888']],\n    keys => {\n      expectType<\n        Equal<\n          [{ a: string; b: { c: string } }, (string | number)[]],\n          typeof keys\n        >\n      >(true)\n      return keys\n    }\n  )\n  useSWR(\n    () => (truthy() ? [{ a: '1', b: { c: '3' } }, [1231, '888']] : null),\n    keys => {\n      expectType<\n        Equal<\n          [{ a: string; b: { c: string } }, (string | number)[]],\n          typeof keys\n        >\n      >(true)\n      return keys\n    }\n  )\n\n  useSWR(\n    () => (truthy() ? [{ a: '1', b: { c: '3' } }, [1231, '888']] : false),\n    keys => {\n      expectType<\n        Equal<\n          [{ a: string; b: { c: string } }, (string | number)[]],\n          typeof keys\n        >\n      >(true)\n      return keys\n    }\n  )\n\n  useSWRInfinite(\n    index => [{ a: '1', b: { c: '3', d: index } }, [1231, '888']],\n    keys => {\n      expectType<\n        Equal<\n          [{ a: string; b: { c: string; d: number } }, (string | number)[]],\n          typeof keys\n        >\n      >(true)\n      return keys[1]\n    }\n  )\n\n  useSWRInfinite(\n    index =>\n      truthy() ? [{ a: '1', b: { c: '3', d: index } }, [1231, '888']] : null,\n    keys => {\n      expectType<\n        Equal<\n          [{ a: string; b: { c: string; d: number } }, (string | number)[]],\n          typeof keys\n        >\n      >(true)\n      return keys[1]\n    }\n  )\n\n  useSWRInfinite(\n    index =>\n      truthy() ? [{ a: '1', b: { c: '3', d: index } }, [1231, '888']] : false,\n    keys => {\n      expectType<\n        Equal<\n          [{ a: string; b: { c: string; d: number } }, (string | number)[]],\n          typeof keys\n        >\n      >(true)\n      return keys[1]\n    }\n  )\n}\n\nexport function useReturnReadonlyTuple() {\n  useSWR(\n    () => [{ a: '1', b: { c: '3' } }, [1231, '888']] as const,\n    keys => {\n      expectType<\n        Equal<\n          readonly [\n            {\n              readonly a: '1'\n              readonly b: {\n                readonly c: '3'\n              }\n            },\n            readonly [1231, '888']\n          ],\n          typeof keys\n        >\n      >(true)\n      return keys\n    }\n  )\n  useSWR(\n    () =>\n      truthy() ? ([{ a: '1', b: { c: '3' } }, [1231, '888']] as const) : null,\n    keys => {\n      expectType<\n        Equal<\n          readonly [\n            {\n              readonly a: '1'\n              readonly b: {\n                readonly c: '3'\n              }\n            },\n            readonly [1231, '888']\n          ],\n          typeof keys\n        >\n      >(true)\n      return keys\n    }\n  )\n\n  useSWR(\n    () =>\n      truthy() ? ([{ a: '1', b: { c: '3' } }, [1231, '888']] as const) : false,\n    keys => {\n      expectType<\n        Equal<\n          readonly [\n            {\n              readonly a: '1'\n              readonly b: {\n                readonly c: '3'\n              }\n            },\n            readonly [1231, '888']\n          ],\n          typeof keys\n        >\n      >(true)\n      return keys\n    }\n  )\n\n  useSWRInfinite(\n    () => [{ a: '1', b: { c: '3' } }, [1231, '888']] as const,\n    keys => {\n      expectType<\n        Equal<\n          readonly [\n            {\n              readonly a: '1'\n              readonly b: {\n                readonly c: '3'\n              }\n            },\n            readonly [1231, '888']\n          ],\n          typeof keys\n        >\n      >(true)\n      return keys\n    }\n  )\n  useSWRInfinite(\n    () =>\n      truthy() ? ([{ a: '1', b: { c: '3' } }, [1231, '888']] as const) : null,\n    keys => {\n      expectType<\n        Equal<\n          readonly [\n            {\n              readonly a: '1'\n              readonly b: {\n                readonly c: '3'\n              }\n            },\n            readonly [1231, '888']\n          ],\n          typeof keys\n        >\n      >(true)\n      return keys[1]\n    }\n  )\n\n  useSWRInfinite(\n    () =>\n      truthy() ? ([{ a: '1', b: { c: '3' } }, [1231, '888']] as const) : false,\n    keys => {\n      expectType<\n        Equal<\n          readonly [\n            {\n              readonly a: '1'\n              readonly b: {\n                readonly c: '3'\n              }\n            },\n            readonly [1231, '888']\n          ],\n          typeof keys\n        >\n      >(true)\n      return keys[1]\n    }\n  )\n}\n"
  },
  {
    "path": "test/type/helper-types.tsx",
    "content": "import type { BlockingData } from 'swr/_internal'\nimport { expectType } from './utils'\n\nexport function testDataCached() {\n  expectType<BlockingData<string, { fallbackData: string }>>(true)\n  expectType<BlockingData<any, { suspense: true }>>(true)\n  expectType<\n    BlockingData<string, { fallbackData?: string; revalidate: boolean }>\n  >(false)\n  expectType<BlockingData<false, { suspense: false; revalidate: boolean }>>(\n    false\n  )\n}\n"
  },
  {
    "path": "test/type/internal.tsx",
    "content": "import { rAF } from 'swr/_internal'\nimport { expectType } from './utils'\n\nexport function rAFTyping() {\n  expectType<\n    (f: (...args: any[]) => void) => ReturnType<typeof setTimeout> | number\n  >(rAF)\n}\n"
  },
  {
    "path": "test/type/mutate.ts",
    "content": "/* eslint-disable @typescript-eslint/no-empty-object-type */\nimport type { Equal, Expect } from '@type-challenges/utils'\nimport useSWR, { useSWRConfig } from 'swr'\nimport type {\n  MutatorFn,\n  Key,\n  MutatorCallback,\n  Mutator,\n  MutatorWrapper,\n  Arguments\n} from 'swr/_internal'\nimport { expectType } from './utils'\n\ntype Case1<Data = any> = MutatorFn<Data>\ntype Case2<Data = any> = (\n  cache: Cache,\n  key: Key,\n  data: Data | Promise<Data> | MutatorCallback<Data>,\n  opts: boolean\n) => Promise<Data | undefined>\ntype Case3<Data = any> = (\n  cache: Cache,\n  key: Key,\n  data: Data | Promise<Data> | MutatorCallback<Data>\n) => Promise<Data | undefined>\ntype Case4<Data = any> = (\n  cache: Cache,\n  key: Key,\n  data: Data | Promise<Data> | MutatorCallback<Data>,\n  opts: {\n    populateCache: undefined\n  }\n) => Promise<Data | undefined>\ntype Case5<Data = any> = (\n  cache: Cache,\n  key: Key,\n  data: Data | Promise<Data> | MutatorCallback<Data>,\n  opts: {\n    populateCache: false\n  }\n) => Promise<Data | undefined>\ntype Case6<Data = any> = (\n  cache: Cache,\n  key: Key,\n  data: Data | Promise<Data> | MutatorCallback<Data>,\n  opts: {\n    populateCache: true\n  }\n) => Promise<Data | undefined>\n\nexport type TestCasesForMutator = [\n  Expect<Equal<Mutator<{}>, Promise<{} | undefined>>>,\n  Expect<Equal<MutatorWrapper<Case1<{}>>, Promise<{} | undefined>>>,\n  Expect<Equal<MutatorWrapper<Case2<{}>>, Promise<{} | undefined>>>,\n  Expect<Equal<MutatorWrapper<Case3<{}>>, Promise<{} | undefined>>>,\n  Expect<Equal<MutatorWrapper<Case4<{}>>, Promise<{} | undefined>>>,\n  Expect<Equal<MutatorWrapper<Case5<{}>>, never>>,\n  Expect<Equal<MutatorWrapper<Case6<{}>>, Promise<{} | undefined>>>\n]\n\nexport function useMutatorTypes() {\n  const { mutate } = useSWR<string>('')\n\n  mutate(async () => '1')\n  mutate(async () => '1', { populateCache: false })\n\n  // @ts-expect-error\n  mutate(async () => 1)\n  // @ts-expect-error\n  mutate(async () => 1, { populateCache: false })\n}\n\nexport function useConfigMutate() {\n  const { mutate } = useSWRConfig()\n  expect<Promise<Array<any>>>(\n    mutate(\n      key => {\n        expectType<Arguments>(key)\n        return typeof key === 'string' && key.startsWith('swr')\n      },\n      data => {\n        expectType<number | undefined>(data)\n        return 0\n      }\n    )\n  )\n\n  expect<Promise<any>>(\n    mutate('string', (data?: string) => {\n      expectType<string | undefined>(data)\n      return '0'\n    })\n  )\n\n  expect<Promise<Array<number | undefined>>>(\n    mutate<number>(\n      key => {\n        expectType<Arguments>(key)\n        return typeof key === 'string' && key.startsWith('swr')\n      },\n      data => {\n        expectType<number | undefined>(data)\n        return 0\n      }\n    )\n  )\n\n  expect<Promise<string | undefined>>(\n    mutate<string>('string', data => {\n      expectType<string | undefined>(data)\n      return '0'\n    })\n  )\n\n  mutate<string>('string', data => {\n    expectType<string | undefined>(data)\n    return '0'\n  })\n}\n"
  },
  {
    "path": "test/type/mutation.ts",
    "content": "import useSWRMutation, { type TriggerWithoutArgs } from 'swr/mutation'\nimport { expectType } from './utils'\n\nexport function useConfigMutation() {\n  const { trigger } = useSWRMutation('key', k => k)\n  expectType<TriggerWithoutArgs<'key', any, string, never>>(trigger)\n}\n"
  },
  {
    "path": "test/type/option-fetcher.ts",
    "content": "import useSWR from 'swr'\nimport useSWRInfinite from 'swr/infinite'\nimport { expectType, truthy } from './utils'\nimport type { Equal } from '@type-challenges/utils'\n\nexport function useDataErrorGeneric() {\n  useSWR<{ id: number }>('/api/', { fetcher: () => ({ id: 123 }) })\n  useSWR<string, any>('/api/', { fetcher: (key: string) => key })\n  useSWRInfinite<number[]>(() => '/api/', { fetcher: key => key })\n}\n\nexport function useString() {\n  useSWR('/api/user', {\n    fetcher: key => {\n      expectType<Equal<'/api/user', typeof key>>(true)\n      return key\n    }\n  })\n  useSWR(truthy() ? '/api/user' : null, {\n    fetcher: key => {\n      expectType<Equal<'/api/user', typeof key>>(true)\n      return key\n    }\n  })\n  useSWR(truthy() ? '/api/user' : false, {\n    fetcher: key => {\n      expectType<Equal<'/api/user', typeof key>>(true)\n      return key\n    }\n  })\n}\n\nexport function useRecord() {\n  useSWR(\n    { a: '1', b: { c: '3', d: 2 } },\n    {\n      fetcher: key => {\n        expectType<\n          Equal<{ a: string; b: { c: string; d: number } }, typeof key>\n        >(true)\n        return key\n      }\n    }\n  )\n\n  useSWR(truthy() ? { a: '1', b: { c: '3', d: 2 } } : null, {\n    fetcher: key => {\n      expectType<Equal<{ a: string; b: { c: string; d: number } }, typeof key>>(\n        true\n      )\n      return key\n    }\n  })\n\n  useSWR(truthy() ? { a: '1', b: { c: '3', d: 2 } } : false, {\n    fetcher: key => {\n      expectType<Equal<{ a: string; b: { c: string; d: number } }, typeof key>>(\n        true\n      )\n      return key\n    }\n  })\n}\n\nexport function useTuple() {\n  useSWR([{ a: '1', b: { c: '3' } }, [1231, '888']], {\n    fetcher: keys => {\n      expectType<\n        Equal<\n          [{ a: string; b: { c: string } }, (string | number)[]],\n          typeof keys\n        >\n      >(true)\n      return keys\n    }\n  })\n  useSWR(truthy() ? [{ a: '1', b: { c: '3' } }, [1231, '888']] : null, {\n    fetcher: keys => {\n      expectType<\n        Equal<\n          [{ a: string; b: { c: string } }, (string | number)[]],\n          typeof keys\n        >\n      >(true)\n      return keys\n    }\n  })\n  useSWR(truthy() ? [{ a: '1', b: { c: '3' } }, [1231, '888']] : false, {\n    fetcher: keys => {\n      expectType<\n        Equal<\n          [{ a: string; b: { c: string } }, (string | number)[]],\n          typeof keys\n        >\n      >(true)\n      return keys\n    }\n  })\n}\n\nexport function useReadonlyTuple() {\n  useSWR([{ a: '1', b: { c: '3' } }, [1231, '888']] as const, {\n    fetcher: keys => {\n      expectType<\n        Equal<\n          readonly [\n            {\n              readonly a: '1'\n              readonly b: {\n                readonly c: '3'\n              }\n            },\n            readonly [1231, '888']\n          ],\n          typeof keys\n        >\n      >(true)\n      return keys\n    }\n  })\n  useSWR(\n    truthy() ? ([{ a: '1', b: { c: '3' } }, [1231, '888']] as const) : null,\n    {\n      fetcher: keys => {\n        expectType<\n          Equal<\n            readonly [\n              {\n                readonly a: '1'\n                readonly b: {\n                  readonly c: '3'\n                }\n              },\n              readonly [1231, '888']\n            ],\n            typeof keys\n          >\n        >(true)\n        return keys\n      }\n    }\n  )\n  useSWR(\n    truthy() ? ([{ a: '1', b: { c: '3' } }, [1231, '888']] as const) : false,\n    {\n      fetcher: keys => {\n        expectType<\n          Equal<\n            readonly [\n              {\n                readonly a: '1'\n                readonly b: {\n                  readonly c: '3'\n                }\n              },\n              readonly [1231, '888']\n            ],\n            typeof keys\n          >\n        >(true)\n        return keys\n      }\n    }\n  )\n}\n\nexport function useReturnString() {\n  useSWR(() => '/api/user', {\n    fetcher: key => {\n      expectType<Equal<string, typeof key>>(true)\n      return key\n    }\n  })\n  useSWR(() => (truthy() ? '/api/user' : null), {\n    fetcher: key => {\n      expectType<Equal<'/api/user', typeof key>>(true)\n      return key\n    }\n  })\n\n  useSWR(() => (truthy() ? '/api/user' : false), {\n    fetcher: key => {\n      expectType<Equal<'/api/user', typeof key>>(true)\n      return key\n    }\n  })\n\n  useSWRInfinite(\n    (index, previousPageData: string) => {\n      return `${index}${previousPageData}`\n    },\n    {\n      fetcher: key => {\n        expectType<Equal<string, typeof key>>(true)\n        return key\n      }\n    }\n  )\n\n  useSWRInfinite(\n    (index, previousPageData: string) => {\n      return truthy() ? `${index}${previousPageData}` : null\n    },\n    {\n      fetcher: key => {\n        expectType<Equal<string, typeof key>>(true)\n        return key\n      }\n    }\n  )\n  useSWRInfinite(\n    (index, previousPageData: string) => {\n      return truthy() ? `${index}${previousPageData}` : false\n    },\n    {\n      fetcher: key => {\n        expectType<Equal<string, typeof key>>(true)\n        return key\n      }\n    }\n  )\n}\n\nexport function useReturnRecord() {\n  useSWR(() => ({ a: '1', b: { c: '3', d: 2 } }), {\n    fetcher: key => {\n      expectType<Equal<{ a: string; b: { c: string; d: number } }, typeof key>>(\n        true\n      )\n      return key\n    }\n  })\n  useSWR(() => (truthy() ? { a: '1', b: { c: '3', d: 2 } } : null), {\n    fetcher: key => {\n      expectType<Equal<{ a: string; b: { c: string; d: number } }, typeof key>>(\n        true\n      )\n      return key\n    }\n  })\n\n  useSWR(() => (truthy() ? { a: '1', b: { c: '3', d: 2 } } : false), {\n    fetcher: key => {\n      expectType<Equal<{ a: string; b: { c: string; d: number } }, typeof key>>(\n        true\n      )\n      return key\n    }\n  })\n\n  useSWRInfinite(\n    index => ({\n      index,\n      endPoint: '/api'\n    }),\n    {\n      fetcher: key => {\n        expectType<Equal<{ index: number; endPoint: string }, typeof key>>(true)\n        return [key]\n      }\n    }\n  )\n\n  useSWRInfinite(\n    index =>\n      truthy()\n        ? {\n            index,\n            endPoint: '/api'\n          }\n        : null,\n    {\n      fetcher: key => {\n        expectType<Equal<{ index: number; endPoint: string }, typeof key>>(true)\n        return [key]\n      }\n    }\n  )\n\n  useSWRInfinite(\n    index =>\n      truthy()\n        ? {\n            index,\n            endPoint: '/api'\n          }\n        : false,\n    {\n      fetcher: key => {\n        expectType<Equal<{ index: number; endPoint: string }, typeof key>>(true)\n        return [key]\n      }\n    }\n  )\n}\n\nexport function useReturnTuple() {\n  useSWR(() => [{ a: '1', b: { c: '3' } }, [1231, '888']], {\n    fetcher: keys => {\n      expectType<\n        Equal<\n          [{ a: string; b: { c: string } }, (string | number)[]],\n          typeof keys\n        >\n      >(true)\n      return keys\n    }\n  })\n  useSWR(() => (truthy() ? [{ a: '1', b: { c: '3' } }, [1231, '888']] : null), {\n    fetcher: keys => {\n      expectType<\n        Equal<\n          [{ a: string; b: { c: string } }, (string | number)[]],\n          typeof keys\n        >\n      >(true)\n      return keys\n    }\n  })\n\n  useSWR(\n    () => (truthy() ? [{ a: '1', b: { c: '3' } }, [1231, '888']] : false),\n    {\n      fetcher: keys => {\n        expectType<\n          Equal<\n            [{ a: string; b: { c: string } }, (string | number)[]],\n            typeof keys\n          >\n        >(true)\n        return keys\n      }\n    }\n  )\n\n  useSWRInfinite(\n    index => [{ a: '1', b: { c: '3', d: index } }, [1231, '888']],\n    {\n      fetcher: keys => {\n        expectType<\n          Equal<\n            [{ a: string; b: { c: string; d: number } }, (string | number)[]],\n            typeof keys\n          >\n        >(true)\n        return keys[1]\n      }\n    }\n  )\n\n  useSWRInfinite(\n    index =>\n      truthy() ? [{ a: '1', b: { c: '3', d: index } }, [1231, '888']] : null,\n    {\n      fetcher: keys => {\n        expectType<\n          Equal<\n            [{ a: string; b: { c: string; d: number } }, (string | number)[]],\n            typeof keys\n          >\n        >(true)\n        return keys[1]\n      }\n    }\n  )\n\n  useSWRInfinite(\n    index =>\n      truthy() ? [{ a: '1', b: { c: '3', d: index } }, [1231, '888']] : false,\n    {\n      fetcher: keys => {\n        expectType<\n          Equal<\n            [{ a: string; b: { c: string; d: number } }, (string | number)[]],\n            typeof keys\n          >\n        >(true)\n        return keys[1]\n      }\n    }\n  )\n}\n\nexport function useReturnReadonlyTuple() {\n  useSWR(() => [{ a: '1', b: { c: '3' } }, [1231, '888']] as const, {\n    fetcher: keys => {\n      expectType<\n        Equal<\n          readonly [\n            {\n              readonly a: '1'\n              readonly b: {\n                readonly c: '3'\n              }\n            },\n            readonly [1231, '888']\n          ],\n          typeof keys\n        >\n      >(true)\n      return keys\n    }\n  })\n  useSWR(\n    () =>\n      truthy() ? ([{ a: '1', b: { c: '3' } }, [1231, '888']] as const) : null,\n    {\n      fetcher: keys => {\n        expectType<\n          Equal<\n            readonly [\n              {\n                readonly a: '1'\n                readonly b: {\n                  readonly c: '3'\n                }\n              },\n              readonly [1231, '888']\n            ],\n            typeof keys\n          >\n        >(true)\n        return keys\n      }\n    }\n  )\n\n  useSWR(\n    () =>\n      truthy() ? ([{ a: '1', b: { c: '3' } }, [1231, '888']] as const) : false,\n    {\n      fetcher: keys => {\n        expectType<\n          Equal<\n            readonly [\n              {\n                readonly a: '1'\n                readonly b: {\n                  readonly c: '3'\n                }\n              },\n              readonly [1231, '888']\n            ],\n            typeof keys\n          >\n        >(true)\n        return keys\n      }\n    }\n  )\n\n  useSWRInfinite(() => [{ a: '1', b: { c: '3' } }, [1231, '888']] as const, {\n    fetcher: keys => {\n      expectType<\n        Equal<\n          readonly [\n            {\n              readonly a: '1'\n              readonly b: {\n                readonly c: '3'\n              }\n            },\n            readonly [1231, '888']\n          ],\n          typeof keys\n        >\n      >(true)\n      return keys\n    }\n  })\n  useSWRInfinite(\n    () =>\n      truthy() ? ([{ a: '1', b: { c: '3' } }, [1231, '888']] as const) : null,\n    {\n      fetcher: keys => {\n        expectType<\n          Equal<\n            readonly [\n              {\n                readonly a: '1'\n                readonly b: {\n                  readonly c: '3'\n                }\n              },\n              readonly [1231, '888']\n            ],\n            typeof keys\n          >\n        >(true)\n        return keys[1]\n      }\n    }\n  )\n\n  useSWRInfinite(\n    () =>\n      truthy() ? ([{ a: '1', b: { c: '3' } }, [1231, '888']] as const) : false,\n    {\n      fetcher: keys => {\n        expectType<\n          Equal<\n            readonly [\n              {\n                readonly a: '1'\n                readonly b: {\n                  readonly c: '3'\n                }\n              },\n              readonly [1231, '888']\n            ],\n            typeof keys\n          >\n        >(true)\n        return keys[1]\n      }\n    }\n  )\n}\n"
  },
  {
    "path": "test/type/preload.ts",
    "content": "import { preload } from 'swr'\nimport { expectType } from './utils'\nimport type { Equal } from '@type-challenges/utils'\n\nexport function testPreload() {\n  const data1 = preload('key', () => Promise.resolve('value' as const))\n  expectType<Equal<Promise<'value'>, typeof data1>>(true)\n\n  const data2 = preload(\n    () => 'key',\n    () => 'value' as const\n  )\n  expectType<Equal<'value', typeof data2>>(true)\n\n  const data3 = preload<'value'>(\n    () => 'key',\n    () => 'value' as const\n  )\n  // specifing a generic param breaks the rest type inference so get FetcherResponse<\"value\">\n  expectType<Equal<'value' | Promise<'value'>, typeof data3>>(true)\n\n  preload('key', key => {\n    expectType<Equal<'key', typeof key>>(true)\n  })\n\n  preload<'value'>(\n    'key',\n    (\n      // @ts-expect-error -- infered any implicitly\n      key\n    ) => {\n      return 'value' as const\n    }\n  )\n\n  preload(['key', 1], keys => {\n    expectType<Equal<[string, number], typeof keys>>(true)\n  })\n\n  preload(\n    () => 'key' as const,\n    key => {\n      expectType<Equal<'key', typeof key>>(true)\n    }\n  )\n}\n"
  },
  {
    "path": "test/type/subscription.ts",
    "content": "import useSWRSubscription from 'swr/subscription'\nimport type { SWRSubscriptionOptions, SWRSubscription } from 'swr/subscription'\nimport { expectType, truthy } from './utils'\n\nexport function useTestSubscription() {\n  useSWRSubscription(\n    'key',\n    (key, { next: _ }: SWRSubscriptionOptions<string, Error>) => {\n      expectType<'key'>(key)\n      return () => {}\n    }\n  )\n  useSWRSubscription(\n    truthy() ? 'key' : undefined,\n    (key, { next: _ }: SWRSubscriptionOptions<string, Error>) => {\n      expectType<'key'>(key)\n      return () => {}\n    }\n  )\n  useSWRSubscription(\n    ['key', 1],\n    (key, { next: _ }: SWRSubscriptionOptions<string, Error>) => {\n      expectType<[string, number]>(key)\n      return () => {}\n    }\n  )\n  useSWRSubscription(\n    truthy() ? ['key', 1] : undefined,\n    (key, { next: _ }: SWRSubscriptionOptions<string, Error>) => {\n      expectType<[string, number]>(key)\n      return () => {}\n    }\n  )\n  useSWRSubscription(\n    { foo: 'bar' },\n    (key, { next: _ }: SWRSubscriptionOptions<string, Error>) => {\n      expectType<{ foo: string }>(key)\n      return () => {}\n    }\n  )\n  useSWRSubscription(\n    truthy() ? { foo: 'bar' } : undefined,\n    (key, { next: _ }: SWRSubscriptionOptions<string, Error>) => {\n      expectType<{ foo: string }>(key)\n      return () => {}\n    }\n  )\n\n  useSWRSubscription(\n    () => 'key',\n    (key, { next: _ }: SWRSubscriptionOptions<string, Error>) => {\n      expectType<string>(key)\n      return () => {}\n    }\n  )\n  useSWRSubscription(\n    () => (truthy() ? 'key' : undefined),\n    (key, { next: _ }: SWRSubscriptionOptions<string, Error>) => {\n      expectType<'key'>(key)\n      return () => {}\n    }\n  )\n  useSWRSubscription(\n    () => ['key', 1],\n    (key, { next: _ }: SWRSubscriptionOptions<string, Error>) => {\n      expectType<[string, number]>(key)\n      return () => {}\n    }\n  )\n  useSWRSubscription(\n    () => (truthy() ? ['key', 1] : undefined),\n    (key, { next: _ }: SWRSubscriptionOptions<string, Error>) => {\n      expectType<[string, number]>(key)\n      return () => {}\n    }\n  )\n  useSWRSubscription(\n    () => ({ foo: 'bar' }),\n    (key, { next: _ }: SWRSubscriptionOptions<string, Error>) => {\n      expectType<{ foo: string }>(key)\n      return () => {}\n    }\n  )\n  useSWRSubscription(\n    () => (truthy() ? { foo: 'bar' } : undefined),\n    (key, { next: _ }: SWRSubscriptionOptions<string, Error>) => {\n      expectType<{ foo: string }>(key)\n      return () => {}\n    }\n  )\n\n  const sub: SWRSubscription<string, string, Error> = (_, { next: __ }) => {\n    return () => {}\n  }\n  const { data: data2, error: error2 } = useSWRSubscription('key', sub)\n  expectType<string | undefined>(data2)\n  expectType<Error | undefined>(error2)\n}\n"
  },
  {
    "path": "test/type/suspense/helper-types.tsx",
    "content": "import type { BlockingData } from 'swr/_internal'\nimport { expectType } from '../utils'\n\ndeclare module 'swr' {\n  interface SWRGlobalConfig {\n    suspense: true\n  }\n}\n\nexport function testDataCached() {\n  expectType<BlockingData<string, { fallbackData: string }>>(true)\n  expectType<BlockingData<any, { suspense: true }>>(true)\n  expectType<\n    BlockingData<string, { fallbackData?: string; revalidate: boolean }>\n  >(true)\n  expectType<BlockingData<false, { suspense: false; revalidate: boolean }>>(\n    true\n  )\n}\n"
  },
  {
    "path": "test/type/suspense/suspense.ts",
    "content": "import useSWR from 'swr'\nimport { expectType } from '../utils'\n\ndeclare module 'swr' {\n  interface SWRGlobalConfig {\n    suspense: true\n  }\n}\n\nexport function useTestSuspense() {\n  const { data } = useSWR('/api', (k: string) => Promise.resolve(k))\n  expectType<string>(data)\n}\n"
  },
  {
    "path": "test/type/suspense/tsconfig.json",
    "content": "{\n  \"extends\": \"../tsconfig.json\",\n  \"compilerOptions\": {\n    \"noEmit\": true,\n    \"strict\": true,\n    \"jsx\": \"react-jsx\"\n  },\n  \"include\": [\"./**/*.ts\", \"./**/*.tsx\"],\n  \"exclude\": []\n}\n"
  },
  {
    "path": "test/type/trigger.ts",
    "content": "import useSWRMutation from 'swr/mutation'\nimport useSWR from 'swr'\ntype ExpectType = <T>(value: T) => void\nconst expectType: ExpectType = () => {}\n\ntype Equal<A, B> = (<T>() => T extends A ? 1 : 2) extends <T>() => T extends B\n  ? 1\n  : 2\n  ? true\n  : false\n\n// Test the Equal type\nexpectType<Equal<number, string>>(false) // should be false\n\nexport function useExtraParam() {\n  useSWRMutation('/api/user', key => {\n    expectType<string>(key)\n  })\n  useSWRMutation('/api/user', (_, opts) => {\n    expectType<Equal<typeof opts, Readonly<{ arg: never }>>>(true)\n  })\n}\n\nexport function useTrigger() {\n  const { trigger, reset, data, error } = useSWRMutation(\n    '/api/user',\n    (_, opts: { arg: number }) => String(opts.arg)\n  )\n\n  // The argument of `trigger` should be number or undefined.\n  expectType<Equal<Parameters<typeof trigger>[0], number>>(true)\n  expectType<Promise<string>>(trigger(1))\n\n  // Other return values\n  expectType<Equal<typeof reset, () => void>>(true)\n  expectType<Equal<typeof data, string | undefined>>(true)\n  expectType<Equal<typeof error, any>>(true)\n\n  // Should not return some fields.\n  type Ret = ReturnType<typeof useSWRMutation>\n  expectType<Equal<Omit<Ret, 'mutate' | 'isValidating'>, Ret>>(true)\n}\n\nexport function useTriggerWithParameter() {\n  const { trigger } = useSWRMutation<string, any, string, number>(\n    '/api/user',\n    (_, opts) => {\n      expectType<Equal<typeof opts, Readonly<{ arg: number }>>>(true)\n      return String(opts.arg)\n    }\n  )\n\n  // The argument of `trigger` should be number or undefined.\n  expectType<Equal<Parameters<typeof trigger>[0], number>>(true)\n  expectType<Promise<string>>(trigger(1))\n  expectType<Promise<string | undefined>>(\n    trigger(1, {\n      throwOnError: false\n    })\n  )\n}\n\nexport function useOnErrorThrowFalse() {\n  const { trigger } = useSWRMutation<string, any, string, number>(\n    '/api/user',\n    (_, opts) => {\n      expectType<Equal<typeof opts, Readonly<{ arg: number }>>>(true)\n      return String(opts.arg)\n    },\n    {\n      throwOnError: false\n    }\n  )\n\n  // The argument of `trigger` should be number or undefined.\n  expectType<Equal<Parameters<typeof trigger>[0], number>>(true)\n  expectType<Promise<string | undefined>>(trigger(1))\n  expectType<Promise<string>>(\n    trigger(1, {\n      throwOnError: true\n    })\n  )\n}\n\nexport function useTestSWRMutation() {\n  const { data } = useSWR('key', async () => {\n    return ['foo']\n  })\n  const { trigger } = useSWRMutation(\n    'key',\n    async (_, { arg }: { arg: 'foo' }) => {\n      return arg.toUpperCase()\n    }\n  )\n\n  const test = () => {\n    // @ts-expect-error `arg` should be 'foo'\n    trigger()\n\n    // @ts-expect-error `arg` should be 'foo'\n    trigger<typeof data>('bar', {\n      optimisticData: current => {\n        expectType<string[] | undefined>(current)\n        return []\n      },\n      populateCache: (added, current) => {\n        expectType<string>(added)\n        expectType<typeof data>(current)\n        return []\n      },\n      revalidate: false\n    })\n  }\n  test()\n}\n\nexport function useTestSWRMutationWithOptionalArgs() {\n  const { trigger } = useSWRMutation(\n    'key',\n    async (_, { arg }: { arg?: 'foo' }) => {\n      return arg?.toUpperCase()\n    }\n  )\n\n  const test = () => {\n    expectType<Promise<string | undefined>>(trigger('foo'))\n    expectType<Promise<string | undefined>>(trigger(undefined))\n    expectType<Promise<string | undefined>>(trigger())\n  }\n  test()\n}\n\nexport function useTestSWRMutationWithSWRMutate() {\n  const { mutate } = useSWR('/some/key', () => {\n    return {\n      foo: 'bar'\n    }\n  })\n  const { trigger } = useSWRMutation('/some/key', () => {\n    return {\n      foo: 'foo'\n    }\n  })\n  const test = () => {\n    // eslint-disable-next-line @typescript-eslint/no-unused-expressions\n    ;async () => {\n      mutate(trigger(), {\n        optimisticData: {\n          foo: 'baz'\n        }\n      })\n    }\n  }\n  test()\n}\n"
  },
  {
    "path": "test/type/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig.json\",\n  \"compilerOptions\": {\n    \"noEmit\": true,\n    \"strict\": true,\n    \"jsx\": \"react-jsx\"\n  },\n  \"include\": [\"./**/*.ts\", \"./**/*.tsx\"],\n  \"exclude\": [\"./suspense\"]\n}\n"
  },
  {
    "path": "test/type/utils.ts",
    "content": "export type ExpectType = <T>(value: T) => void\nexport const expectType: ExpectType = () => {}\n\nexport const truthy: () => boolean = () => true\n"
  },
  {
    "path": "test/unit/serialize.test.ts",
    "content": "/**\n * @jest-environment @edge-runtime/jest-environment\n */\nimport { unstable_serialize } from 'swr'\nimport { stableHash } from 'swr/_internal'\n\ndescribe('SWR - unstable_serialize', () => {\n  it('should serialize arguments correctly', async () => {\n    expect(unstable_serialize([])).toBe('')\n    expect(unstable_serialize(null)).toBe('')\n    expect(unstable_serialize('key')).toBe('key')\n    expect(unstable_serialize([1, { foo: 2, bar: 1 }, ['a', 'b', 'c']])).toBe(\n      stableHash([1, { foo: 2, bar: 1 }, ['a', 'b', 'c']])\n    )\n  })\n})\n"
  },
  {
    "path": "test/unit/utils.test.tsx",
    "content": "/**\n * @jest-environment @edge-runtime/jest-environment\n */\nimport {\n  stableHash as hash,\n  serialize,\n  normalize,\n  mergeConfigs\n} from 'swr/_internal'\n\ndescribe('Utils', () => {\n  it('should normalize arguments correctly', async () => {\n    const fetcher = () => {}\n    const opts = { revalidateOnFocus: false }\n\n    // Only the `key` argument is passed\n    expect(normalize(['key'])).toEqual(['key', null, {}])\n\n    // `key` and `null` as fetcher (no fetcher)\n    expect(normalize(['key', null])).toEqual(['key', null, {}])\n\n    // `key` and `fetcher`\n    expect(normalize(['key', fetcher])).toEqual(['key', fetcher, {}])\n\n    // `key` and `options`\n    expect(normalize(['key', opts])).toEqual(['key', null, opts])\n\n    // `key`, `null` as fetcher, and `options`\n    expect(normalize(['key', null, opts])).toEqual(['key', null, opts])\n\n    // `key`, `fetcher`, and `options`\n    expect(normalize(['key', fetcher, opts])).toEqual(['key', fetcher, opts])\n  })\n\n  it('should hash arguments correctly', async () => {\n    // Empty\n    expect(serialize([])[0]).toEqual('')\n\n    // Primitives\n    expect(hash(['key'])).toEqual('@\"key\",')\n    expect(hash([1])).toEqual('@1,')\n    expect(hash(['false'])).toEqual('@\"false\",')\n    expect(hash([false])).toEqual('@false,')\n    expect(hash([true])).toEqual('@true,')\n    expect(hash([null])).toEqual('@null,')\n    expect(hash(['null'])).toEqual('@\"null\",')\n    expect(hash([undefined])).toEqual('@undefined,')\n    expect(hash([NaN])).toEqual('@NaN,')\n    expect(hash([Infinity])).toEqual('@Infinity,')\n    expect(hash([''])).toEqual('@\"\",')\n\n    // Encodes `\"`\n    expect(hash(['\",\"', 1])).not.toEqual(hash(['', '', 1]))\n\n    // BigInt\n    expect(hash([BigInt(1)])).toEqual('@1,')\n\n    // Date\n    const date = new Date()\n    expect(hash([date])).toEqual(`@${date.toJSON()},`)\n    expect(hash([new Date(1234)])).toEqual(hash([new Date(1234)]))\n\n    // Regex\n    expect(hash([/regex/])).toEqual('@/regex/,')\n\n    // Symbol\n    expect(hash([Symbol('key')])).toMatch('@Symbol(key),')\n    const symbol = Symbol('foo')\n    expect(hash([symbol])).toMatch(hash([symbol]))\n\n    // Due to serialization, those three are equivalent\n    expect(hash([Symbol.for('key')])).toMatch(hash([Symbol.for('key')]))\n    expect(hash([Symbol('key')])).toMatch(hash([Symbol('key')]))\n    expect(hash([Symbol('key')])).toMatch(hash([Symbol.for('key')]))\n\n    // Set, Map, Buffer...\n    const set = new Set()\n    expect(hash([set])).not.toMatch(hash([new Set()]))\n    expect(hash([set])).toMatch(hash([set]))\n    const map = new Map()\n    expect(hash([map])).not.toMatch(hash([new Map()]))\n    expect(hash([map])).toMatch(hash([map]))\n    const buffer = new ArrayBuffer(0)\n    expect(hash([buffer])).not.toMatch(hash([new ArrayBuffer(0)]))\n    expect(hash([buffer])).toMatch(hash([buffer]))\n\n    // Serializable objects\n    expect(hash([{ x: 1 }])).toEqual('@#x:1,,')\n    expect(hash([{ '': 1 }])).toEqual('@#:1,,')\n    expect(hash([{ x: { y: 2 } }])).toEqual('@#x:#y:2,,,')\n    expect(hash([[]])).toEqual('@@,')\n    expect(hash([[[]]])).not.toMatch(hash([[], []]))\n\n    // Circular\n    const o: any = {}\n    o.o = o\n    expect(hash([o])).toEqual(hash([o]))\n    expect(hash([o])).not.toEqual(hash([{}]))\n    const a: any = []\n    a.push(a)\n    expect(hash([a])).toEqual(hash([a]))\n    expect(hash([a])).not.toEqual(hash([[]]))\n    const o2: any = {}\n    const a2: any = [o2]\n    o2.a = a2\n    expect(hash([o2])).toEqual(hash([o2]))\n\n    // Unserializable objects\n    expect(hash([() => {}])).toMatch(/@\\d+~,/)\n    expect(hash([class {}])).toMatch(/@\\d+~,/)\n  })\n\n  it('should always generate the same and stable hash', async () => {\n    // Multiple arguments\n    expect(hash([() => {}, 1, 'key', null, { x: 1 }])).toMatch(\n      /@\\d+~,1,\"key\",null,#x:1,,/\n    )\n\n    // Stable hash\n    expect(hash([{ x: 1, y: 2, z: undefined }])).toMatch(\n      hash([{ z: undefined, y: 2, x: 1 }])\n    )\n    expect(hash([{ x: 1, y: { a: 1, b: 2 }, z: undefined }])).toMatch(\n      hash([{ y: { b: 2, a: 1 }, x: 1 }])\n    )\n\n    // Same hash of the same reference\n    const f = () => {}\n    expect(hash([f])).toEqual(hash([f]))\n    expect(hash([() => {}])).not.toEqual(hash([() => {}]))\n  })\n\n  it('should correctly merge configs', async () => {\n    const a: any = { a: 1 },\n      b: any = { b: 1 }\n\n    // Should merge middleware\n    expect(mergeConfigs({ use: [a] }, { use: [b] })).toEqual({ use: [a, b] })\n\n    // Should merge fallback\n    expect(mergeConfigs({ fallback: a }, { fallback: b })).toEqual({\n      fallback: { a: 1, b: 1 }\n    })\n  })\n})\n"
  },
  {
    "path": "test/unit/web-preset.test.ts",
    "content": "import { EventEmitter } from 'events'\n\nconst FOCUS_EVENT = 'focus'\nconst VISIBILITYCHANGE_EVENT = 'visibilitychange'\n\nfunction createEventTarget() {\n  EventEmitter.prototype['addEventListener'] = EventEmitter.prototype.on\n  EventEmitter.prototype['removeEventListener'] = EventEmitter.prototype.off\n  const target = new EventEmitter()\n\n  return target\n}\n\nfunction runTests(propertyName) {\n  let initFocus\n  const eventName =\n    propertyName === 'window' ? FOCUS_EVENT : VISIBILITYCHANGE_EVENT\n\n  describe(`Web Preset ${propertyName}`, () => {\n    const globalSpy = {\n      window: undefined,\n      document: undefined\n    }\n\n    beforeEach(() => {\n      globalSpy.window = jest.spyOn(global, 'window', 'get')\n      globalSpy.document = jest.spyOn(global, 'document', 'get')\n\n      jest.resetModules()\n    })\n\n    afterEach(() => {\n      globalSpy.window.mockClear()\n      globalSpy.document.mockClear()\n    })\n\n    it(`should trigger listener when ${propertyName} has browser APIs`, async () => {\n      const target = createEventTarget()\n      if (propertyName === 'window') {\n        globalSpy.window.mockImplementation(() => target)\n        globalSpy.document.mockImplementation(() => undefined)\n      } else if (propertyName === 'document') {\n        globalSpy.window.mockImplementation(() => undefined)\n        globalSpy.document.mockImplementation(() => target)\n      }\n\n      // eslint-disable-next-line @typescript-eslint/no-require-imports\n      initFocus = require('swr/_internal').defaultConfigOptions.initFocus\n\n      const fn = jest.fn()\n      const release = initFocus(fn) as () => void\n\n      target.emit(eventName)\n      expect(fn).toHaveBeenCalledTimes(1)\n\n      release()\n      target.emit(eventName)\n      expect(fn).toHaveBeenCalledTimes(1)\n    })\n\n    it(`should not trigger listener when ${propertyName} is falsy`, async () => {\n      if (propertyName === 'window') {\n        // window exists but without event APIs\n        globalSpy.window.mockImplementation(() => ({\n          emit: createEventTarget().emit\n        }))\n        globalSpy.document.mockImplementation(() => undefined)\n      } else if (propertyName === 'document') {\n        globalSpy.window.mockImplementation(() => undefined)\n        globalSpy.document.mockImplementation(() => undefined)\n      }\n\n      // eslint-disable-next-line @typescript-eslint/no-require-imports\n      initFocus = require('swr/_internal').defaultConfigOptions.initFocus\n\n      const fn = jest.fn()\n      const release = initFocus(fn) as () => void\n      const target = global[propertyName]\n\n      target?.emit?.(eventName)\n\n      expect(fn).toHaveBeenCalledTimes(0)\n\n      release()\n      if (target && target.emit) {\n        target.emit(eventName)\n      }\n      expect(fn).toHaveBeenCalledTimes(0)\n    })\n  })\n}\n\nrunTests('window')\nrunTests('document')\n"
  },
  {
    "path": "test/use-swr-cache.test.tsx",
    "content": "import { fireEvent, screen } from '@testing-library/react'\nimport { act, useState, StrictMode } from 'react'\nimport useSWR, { useSWRConfig, SWRConfig, mutate as globalMutate } from 'swr'\nimport {\n  sleep,\n  createKey,\n  createResponse,\n  nextTick,\n  focusOn,\n  renderWithConfig,\n  renderWithGlobalCache\n} from './utils'\n\ndescribe('useSWR - cache provider', () => {\n  let provider\n\n  beforeEach(() => {\n    provider = new Map()\n  })\n\n  it('should be able to update the cache', async () => {\n    const fetcher = _key => 'res:' + _key\n    const keys = [createKey(), createKey()]\n\n    function Page() {\n      const [index, setIndex] = useState(0)\n      const { data } = useSWR(keys[index], fetcher)\n\n      return <div onClick={() => setIndex(1)}>{data}</div>\n    }\n\n    renderWithConfig(<Page />, { provider: () => provider })\n    await screen.findByText(fetcher(keys[0]))\n\n    expect(provider.get(keys[1])?.data).toBe(undefined)\n    fireEvent.click(screen.getByText(fetcher(keys[0])))\n    await act(() => sleep(10))\n\n    expect(provider.get(keys[0])?.data).toBe(fetcher(keys[0]))\n    expect(provider.get(keys[1])?.data).toBe(fetcher(keys[1]))\n  })\n\n  it('should be able to read from the initial cache with updates', async () => {\n    const key = createKey()\n    const renderedValues = []\n    const fetcher = () => createResponse('updated value', { delay: 10 })\n\n    function Page() {\n      const { data } = useSWR(key, fetcher)\n      renderedValues.push(data)\n      return <div>{data}</div>\n    }\n\n    renderWithConfig(<Page />, {\n      provider: () => new Map([[key, { data: 'cached value' }]])\n    })\n    screen.getByText('cached value')\n    await screen.findByText('updated value')\n    expect(renderedValues.length).toBe(2)\n  })\n\n  it('should correctly mutate the cached value', async () => {\n    const key = createKey()\n    let mutate\n\n    function Page() {\n      const { mutate: mutateWithCache } = useSWRConfig()\n      mutate = mutateWithCache\n      const { data } = useSWR(key, null)\n      return <div>{data}</div>\n    }\n\n    renderWithConfig(<Page />, {\n      provider: () => new Map([[key, { data: 'cached value' }]])\n    })\n    screen.getByText('cached value')\n    await act(() => mutate(key, 'mutated value', false))\n    await screen.findByText('mutated value')\n  })\n\n  it('should support multi-level cache', async () => {\n    const key = createKey()\n\n    // Nested components with the same cache key can get different values.\n    function Foo() {\n      const { data } = useSWR(key, null)\n      return <>{data}</>\n    }\n    function Page() {\n      const { data } = useSWR(key, null)\n      return (\n        <div>\n          {data}:\n          <SWRConfig\n            value={{ provider: () => new Map([[key, { data: '2' }]]) }}\n          >\n            <Foo />\n          </SWRConfig>\n        </div>\n      )\n    }\n\n    renderWithConfig(<Page />, {\n      provider: () => new Map([[key, { data: '1' }]])\n    })\n    screen.getByText('1:2')\n  })\n\n  it('should support isolated cache', async () => {\n    const key = createKey()\n\n    // Nested components with the same cache key can get different values.\n    function Foo() {\n      const { data } = useSWR(key, null)\n      return <>{data}</>\n    }\n    function Page() {\n      return (\n        <div>\n          <SWRConfig\n            value={{ provider: () => new Map([[key, { data: '1' }]]) }}\n          >\n            <Foo />\n          </SWRConfig>\n          :\n          <SWRConfig\n            value={{ provider: () => new Map([[key, { data: '2' }]]) }}\n          >\n            <Foo />\n          </SWRConfig>\n        </div>\n      )\n    }\n\n    renderWithConfig(<Page />)\n    screen.getByText('1:2')\n  })\n\n  it('should respect provider options', async () => {\n    const key = createKey()\n    const focusFn = jest.fn()\n    const unsubscribeFocusFn = jest.fn()\n    const unsubscribeReconnectFn = jest.fn()\n\n    let value = 1\n    function Page() {\n      const { data } = useSWR(key, () => value++, {\n        dedupingInterval: 0\n      })\n      return <>{String(data)}</>\n    }\n    const { unmount } = renderWithConfig(<Page />, {\n      provider: () => new Map([[key, { data: 0 }]]),\n      initFocus() {\n        focusFn()\n        return unsubscribeFocusFn\n      },\n      initReconnect() {\n        /* do nothing */\n        return unsubscribeReconnectFn\n      }\n    })\n    screen.getByText('0')\n\n    // mount\n    await screen.findByText('1')\n    await nextTick()\n    // try to trigger revalidation, but shouldn't work\n    await focusOn(window)\n    // revalidateOnFocus won't work\n    screen.getByText('1')\n    unmount()\n    expect(focusFn).toHaveBeenCalled()\n    expect(unsubscribeFocusFn).toHaveBeenCalledTimes(1)\n    expect(unsubscribeReconnectFn).toHaveBeenCalledTimes(1)\n  })\n\n  it('should work with revalidateOnFocus', async () => {\n    const key = createKey()\n    let value = 0\n    function Page() {\n      const { data } = useSWR(key, () => value++, {\n        dedupingInterval: 0,\n        focusThrottleInterval: 0\n      })\n      return <>{String(data)}</>\n    }\n\n    renderWithConfig(<Page />, { provider: () => provider })\n    screen.getByText('undefined')\n\n    await screen.findByText('0')\n    await nextTick()\n    await focusOn(window)\n    await nextTick()\n    screen.getByText('1')\n  })\n\n  it('should support fallback values with custom provider', async () => {\n    const key = createKey()\n    function Page() {\n      const { data, isLoading } = useSWR(key, async () => {\n        await sleep(10)\n        return 'data'\n      })\n      return (\n        <>\n          {String(data)},{String(isLoading)}\n        </>\n      )\n    }\n\n    renderWithConfig(<Page />, {\n      provider: () => provider,\n      fallback: { [key]: 'fallback' }\n    })\n    screen.getByText('fallback,true') // no `undefined`, directly fallback\n    await screen.findByText('data,false')\n  })\n\n  it('should not return the fallback if cached', async () => {\n    const key = createKey()\n    function Page() {\n      const { data } = useSWR(key, async () => {\n        await sleep(10)\n        return 'data'\n      })\n      return <>{String(data)}</>\n    }\n\n    renderWithConfig(<Page />, {\n      provider: () => new Map([[key, { data: 'cache' }]]),\n      fallback: { [key]: 'fallback' }\n    })\n    screen.getByText('cache') // no `undefined`, directly from the cache\n    await screen.findByText('data')\n  })\n\n  it('should be able to extend the parent cache', async () => {\n    let parentCache\n\n    const key = createKey()\n    function Page() {\n      const { data } = useSWR(key, async () => {\n        await sleep(10)\n        return 'data'\n      })\n      return <>{String(data)}</>\n    }\n\n    renderWithConfig(<Page />, {\n      provider: parentCache_ => {\n        parentCache = parentCache_\n        return {\n          keys: () => parentCache.keys(),\n          set: (k, v) => parentCache_.set(k, v),\n          get: k => {\n            // We append `-extended` to the value returned by the parent cache.\n            const v = parentCache_.get(k)\n            if (v && typeof v.data !== 'undefined') {\n              return { ...v, data: v.data + '-extended' }\n            }\n            return v\n          },\n          delete: k => parentCache_.delete(k)\n        }\n      }\n    })\n    expect(parentCache).toBe(SWRConfig.defaultValue.cache)\n\n    screen.getByText('undefined')\n    await screen.findByText('data-extended')\n  })\n\n  it('should return the cache instance from the useSWRConfig', async () => {\n    let cache\n    function Page() {\n      cache = useSWRConfig().cache\n      return null\n    }\n\n    renderWithConfig(<Page />, { provider: () => provider })\n    expect(provider).toBe(cache)\n  })\n\n  it('should retain the correct cache hierarchy', async () => {\n    const key = createKey()\n    const fetcher = async () => {\n      await sleep(10)\n      return 'data'\n    }\n\n    function Foo() {\n      const { data } = useSWR(key, fetcher)\n      return <>{String(data)}</>\n    }\n    function Bar() {\n      const { data } = useSWR(key, fetcher)\n      return <>{String(data)}</>\n    }\n    function Page() {\n      const { data } = useSWR(key, fetcher)\n      return (\n        <div>\n          {String(data)},\n          <SWRConfig value={{ fallback: { [key]: 'fallback' } }}>\n            <Foo />\n          </SWRConfig>\n          ,\n          <Bar />\n        </div>\n      )\n    }\n\n    renderWithConfig(<Page />)\n    screen.getByText('undefined,fallback,undefined')\n    await screen.findByText('data,data,data')\n  })\n\n  it('should not recreate the cache if rerendering', async () => {\n    const createCacheProvider = jest.fn()\n    let rerender\n\n    function Page() {\n      rerender = useState({})[1]\n      return (\n        <SWRConfig\n          value={{\n            provider: () => {\n              createCacheProvider()\n              return provider\n            }\n          }}\n        />\n      )\n    }\n\n    renderWithConfig(<Page />)\n    expect(createCacheProvider).toHaveBeenCalledTimes(1)\n    act(() => rerender({}))\n    expect(createCacheProvider).toHaveBeenCalledTimes(1)\n  })\n})\n\ndescribe('useSWR - global cache', () => {\n  it('should return the global cache and mutate by default', async () => {\n    let localCache, localMutate\n    function Page() {\n      const { cache, mutate } = useSWRConfig()\n      localCache = cache\n      localMutate = mutate\n      return null\n    }\n\n    renderWithGlobalCache(<Page />)\n    expect(localCache).toBe(SWRConfig.defaultValue.cache)\n    expect(localMutate).toBe(globalMutate)\n  })\n\n  it('should be able to update the cache', async () => {\n    const fetcher = _key => 'res:' + _key\n    const keys = [createKey(), createKey()]\n\n    let cache\n    function Page() {\n      const [index, setIndex] = useState(0)\n      cache = useSWRConfig().cache\n      const { data } = useSWR(keys[index], fetcher)\n\n      return <div onClick={() => setIndex(1)}>{data}</div>\n    }\n\n    renderWithGlobalCache(<Page />)\n    await screen.findByText(fetcher(keys[0]))\n\n    expect(cache.get(keys[1])?.data).toBe(undefined)\n    fireEvent.click(screen.getByText(fetcher(keys[0])))\n    await act(() => sleep(10))\n\n    expect(cache.get(keys[0])?.data).toBe(fetcher(keys[0]))\n    expect(cache.get(keys[1])?.data).toBe(fetcher(keys[1]))\n  })\n\n  it('should correctly mutate the cached value', async () => {\n    const key = createKey()\n    let mutate\n\n    function Page() {\n      const { mutate: mutateWithCache } = useSWRConfig()\n      mutate = mutateWithCache\n      const { data } = useSWR(key, null)\n      return <div>data:{data}</div>\n    }\n\n    renderWithGlobalCache(<Page />)\n    screen.getByText('data:')\n    await act(() => mutate(key, 'mutated value', false))\n    await screen.findByText('data:mutated value')\n  })\n\n  it('should work with revalidateOnFocus', async () => {\n    const key = createKey()\n    let value = 0\n    function Page() {\n      const { data } = useSWR(key, () => value++, {\n        dedupingInterval: 0,\n        focusThrottleInterval: 0\n      })\n      return <>{String(data)}</>\n    }\n    renderWithGlobalCache(<Page />)\n    screen.getByText('undefined')\n\n    await screen.findByText('0')\n    await nextTick()\n    await focusOn(window)\n    await nextTick()\n    screen.getByText('1')\n  })\n\n  it('should support fallback values', async () => {\n    const key = createKey()\n    function Page() {\n      const { data } = useSWR(key, async () => {\n        await sleep(10)\n        return 'data'\n      })\n      return <>{String(data)}</>\n    }\n\n    renderWithGlobalCache(<Page />, { fallback: { [key]: 'fallback' } })\n    screen.getByText('fallback') // no `undefined`, directly fallback\n    await screen.findByText('data')\n  })\n\n  it('should reusing the same cache instance after unmounting SWRConfig', async () => {\n    let focusEventRegistered = false\n\n    const cacheSingleton = new Map([['key', { data: 'value' }]])\n    function Page() {\n      return (\n        <SWRConfig\n          value={{\n            provider: () => cacheSingleton,\n            initFocus: () => {\n              focusEventRegistered = true\n              return () => (focusEventRegistered = false)\n            }\n          }}\n        >\n          <Comp />\n        </SWRConfig>\n      )\n    }\n    function Comp() {\n      const { cache } = useSWRConfig()\n      return <>{String(cache.get('key')?.data)}</>\n    }\n\n    function Wrapper() {\n      const [mount, setMountPage] = useState(true)\n      return (\n        <>\n          <button onClick={() => setMountPage(!mount)}>toggle</button>\n          {mount ? <Page /> : null}\n        </>\n      )\n    }\n\n    renderWithGlobalCache(<Wrapper />)\n    await screen.findByText('value')\n    fireEvent.click(screen.getByText('toggle'))\n    fireEvent.click(screen.getByText('toggle'))\n    await screen.findByText('value')\n\n    expect(focusEventRegistered).toEqual(true)\n  })\n\n  it('should correctly return the cache instance under strict mode', async () => {\n    function Page() {\n      // Intentionally do this.\n      const [cache] = useState(new Map([['key', { data: 'value' }]]))\n      return (\n        <SWRConfig value={{ provider: () => cache }}>\n          <Comp />\n        </SWRConfig>\n      )\n    }\n    function Comp() {\n      const { cache } = useSWRConfig()\n      return <>{String(cache.get('key')?.data)}</>\n    }\n\n    renderWithGlobalCache(\n      <StrictMode>\n        <Page />\n      </StrictMode>\n    )\n    await screen.findByText('value')\n  })\n})\n"
  },
  {
    "path": "test/use-swr-concurrent-rendering.test.tsx",
    "content": "import { screen } from '@testing-library/react'\nimport {\n  createKey,\n  createResponse,\n  sleep,\n  executeWithoutBatching,\n  renderWithConfig\n} from './utils'\nimport React, { act } from 'react'\n\nimport useSWR from 'swr'\n\ndescribe('useSWR - concurrent rendering', () => {\n  it('should fetch data in concurrent rendering', async () => {\n    const key = createKey()\n    function Page() {\n      const { data } = useSWR(key, () => createResponse('0', { delay: 50 }), {\n        dedupingInterval: 0\n      })\n      return <div>data:{data}</div>\n    }\n\n    renderWithConfig(<Page />)\n\n    screen.getByText('data:')\n    await act(() => sleep(100))\n    screen.getByText('data:0')\n  })\n\n  // https://codesandbox.io/s/concurrent-swr-case-ii-lr6x4u\n  it.skip('should do state updates in transitions', async () => {\n    const key1 = createKey()\n    const key2 = createKey()\n\n    const log = []\n\n    function Counter() {\n      const [count, setCount] = React.useState(0)\n\n      React.useEffect(() => {\n        const interval = setInterval(() => {\n          setCount(x => x + 1)\n        }, 20)\n        return () => clearInterval(interval)\n      }, [])\n\n      log.push(count)\n\n      return <>{count}</>\n    }\n\n    function Body() {\n      useSWR(key2, () => createResponse(true, { delay: 1000 }), {\n        revalidateOnFocus: false,\n        revalidateOnReconnect: false,\n        dedupingInterval: 0,\n        suspense: true\n      })\n      return null\n    }\n\n    function Page() {\n      const { data } = useSWR(key1, () => createResponse(true, { delay: 50 }), {\n        revalidateOnFocus: false,\n        revalidateOnReconnect: false,\n        dedupingInterval: 0\n      })\n\n      return (\n        <>\n          <Counter />\n          {data ? <Body /> : null}\n        </>\n      )\n    }\n\n    await executeWithoutBatching(async () => {\n      renderWithConfig(<Page />)\n      await sleep(500)\n    })\n  })\n})\n"
  },
  {
    "path": "test/use-swr-config-callbacks.test.tsx",
    "content": "import { screen, fireEvent } from '@testing-library/react'\nimport { act } from 'react'\nimport useSWR from 'swr'\nimport { sleep, createResponse, renderWithConfig, createKey } from './utils'\n\ndescribe('useSWR - config callbacks', () => {\n  it('should trigger the onSuccess event with the latest version of the onSuccess callback', async () => {\n    let state = null\n    let count = 0\n    const key = createKey()\n    function Page(props: { text: string }) {\n      const { data, mutate } = useSWR(key, () => createResponse(count++), {\n        onSuccess: () => (state = props.text)\n      })\n      return (\n        <div onClick={() => mutate()}>\n          hello, {data}, {props.text}\n        </div>\n      )\n    }\n    const { rerender } = renderWithConfig(<Page text={'a'} />)\n    // the onSuccess callback does not trigger yet, the state is still null.\n    screen.getByText('hello, , a')\n    expect(state).toEqual(null)\n\n    await screen.findByText('hello, 0, a')\n\n    expect(state).toEqual('a')\n\n    // props changed, but the onSuccess callback does not trigger yet, `state` is same as before\n    rerender(<Page text={'b'} />)\n    screen.getByText('hello, 0, b')\n    expect(state).toEqual('a')\n\n    // trigger revalidation, this would re-trigger the onSuccess callback\n    fireEvent.click(screen.getByText(/hello/))\n    await screen.findByText('hello, 1, b')\n    // the onSuccess callback should capture the latest `props.text`\n    expect(state).toEqual('b')\n  })\n\n  it('should trigger the onError event with the latest version of the onError callback', async () => {\n    let state = null\n    let count = 0\n    const key = createKey()\n    function Page(props: { text: string }) {\n      const { data, mutate, error } = useSWR(\n        key,\n        () => createResponse(new Error(`Error: ${count++}`)),\n        { onError: () => (state = props.text) }\n      )\n      if (error)\n        return (\n          <div title={props.text} onClick={() => mutate()}>\n            {error.message}\n          </div>\n        )\n      return (\n        <div onClick={() => mutate()}>\n          <>\n            hello, {data}, {props.text}\n          </>\n        </div>\n      )\n    }\n\n    const { rerender } = renderWithConfig(<Page text=\"a\" />)\n\n    screen.getByText('hello, , a')\n    expect(state).toEqual(null)\n    await screen.findByText('Error: 0')\n\n    expect(state).toEqual('a')\n\n    // props changed, but the onError callback does not trigger yet.\n    rerender(<Page text=\"b\" />)\n    screen.getByText('Error: 0')\n    screen.getByTitle('b')\n    expect(state).toEqual('a')\n\n    fireEvent.click(screen.getByTitle('b'))\n    await screen.findByText('Error: 1')\n    screen.getByTitle('b')\n    expect(state).toEqual('b')\n  })\n\n  it('should trigger the onErrorRetry event with the latest version of the onErrorRetry callback', async () => {\n    let state = null\n    let count = 0\n    const key = createKey()\n    function Page(props: { text: string }) {\n      const { data, error } = useSWR(\n        key,\n        () => createResponse(new Error(`Error: ${count++}`)),\n        {\n          onErrorRetry: (_, __, ___, revalidate, revalidateOpts) => {\n            state = props.text\n            revalidate(revalidateOpts)\n          }\n        }\n      )\n      if (error) return <div title={props.text}>{error.message}</div>\n      return (\n        <div>\n          <>\n            hello, {data}, {props.text}\n          </>\n        </div>\n      )\n    }\n\n    const { rerender } = renderWithConfig(<Page text=\"a\" />)\n    screen.getByText('hello, , a')\n    expect(state).toEqual(null)\n\n    await screen.findByText('Error: 0')\n    screen.getByTitle('a')\n    expect(state).toEqual('a')\n\n    // since the onErrorRetry schedule a timer to trigger revalidation, and update props.text now\n    rerender(<Page text=\"b\" />)\n    // not revalidated yet.\n    screen.getByText('Error: 0')\n    screen.getByTitle('b')\n    expect(state).toEqual('a')\n\n    // revalidate\n    await screen.findByText('Error: 1')\n    screen.getByTitle('b')\n    expect(state).toEqual('b')\n  })\n\n  it('should trigger the onLoadingSlow and onSuccess event with the lastest version of the callbacks', async () => {\n    const LOADING_TIMEOUT = 100\n    let state = null\n    let count = 0\n    const key = createKey()\n    function Page(props: { text: string }) {\n      const { data } = useSWR(\n        key,\n        () => createResponse(count++, { delay: LOADING_TIMEOUT * 2 }),\n        {\n          onLoadingSlow: () => {\n            state = props.text\n          },\n          onSuccess: () => {\n            state = props.text\n          },\n          loadingTimeout: LOADING_TIMEOUT\n        }\n      )\n      return (\n        <div>\n          hello, {data}, {props.text}\n        </div>\n      )\n    }\n\n    const { rerender } = renderWithConfig(<Page text=\"a\" />)\n\n    screen.getByText('hello, , a')\n    expect(state).toEqual(null)\n\n    // should trigger a loading slow event\n    await act(() => sleep(LOADING_TIMEOUT * 1.5))\n    screen.getByText('hello, , a')\n    expect(state).toEqual('a')\n\n    // onSuccess callback should be called with the latest prop value\n    rerender(<Page text=\"b\" />)\n    await screen.findByText('hello, 0, b')\n    expect(state).toEqual('b')\n  })\n\n  it('should trigger the onDiscarded callback when having a race condition with mutate', async () => {\n    const key = createKey()\n    const discardedEvents = []\n\n    function Page() {\n      const { mutate } = useSWR(\n        key,\n        () => createResponse('foo', { delay: 50 }),\n        {\n          onDiscarded: k => {\n            discardedEvents.push(k)\n          }\n        }\n      )\n      return <div onClick={() => mutate('bar')}>mutate</div>\n    }\n\n    renderWithConfig(<Page />)\n\n    screen.getByText('mutate')\n    await act(() => sleep(10))\n    fireEvent.click(screen.getByText('mutate'))\n    await act(() => sleep(80))\n\n    // Should have one event recorded.\n    expect(discardedEvents).toEqual([key])\n  })\n\n  it('should not trigger the onSuccess callback when discarded', async () => {\n    const key = createKey()\n    const discardedEvents = []\n    const successEvents = []\n\n    function Page() {\n      const { mutate } = useSWR(\n        key,\n        () => createResponse('foo', { delay: 50 }),\n        {\n          onDiscarded: k => {\n            discardedEvents.push(k)\n          },\n          onSuccess: d => {\n            successEvents.push(d)\n          }\n        }\n      )\n      return <div onClick={() => mutate('bar', false)}>mutate</div>\n    }\n\n    renderWithConfig(<Page />)\n\n    screen.getByText('mutate')\n    await act(() => sleep(10))\n    fireEvent.click(screen.getByText('mutate'))\n    await act(() => sleep(80))\n\n    // Should have one event recorded.\n    expect(discardedEvents).toEqual([key])\n    expect(successEvents).toEqual([])\n  })\n})\n"
  },
  {
    "path": "test/use-swr-config.test.tsx",
    "content": "import { screen, fireEvent } from '@testing-library/react'\nimport { useEffect, useState, act } from 'react'\nimport type { Middleware } from 'swr'\nimport useSWR, { SWRConfig, useSWRConfig } from 'swr'\nimport {\n  renderWithConfig,\n  createKey,\n  renderWithGlobalCache,\n  sleep\n} from './utils'\n\ndescribe('useSWR - configs', () => {\n  it('should read the config fallback from the context', async () => {\n    let value = 0\n    const INTERVAL = 100\n    const fetcher = () => value++\n    const key = createKey()\n\n    function Page() {\n      const { data } = useSWR(key)\n      return <div>data: {data}</div>\n    }\n    renderWithConfig(<Page />, {\n      fetcher,\n      refreshInterval: INTERVAL,\n      dedupingInterval: 0\n    })\n    // hydration\n    screen.getByText('data:')\n    // mount\n    await screen.findByText('data: 0')\n\n    // wait for the refresh interval\n    await screen.findByText('data: 1')\n  })\n\n  it('should stop revalidations when config.isPaused returns true', async () => {\n    const key = createKey()\n    let value = 0\n    const fetcher = () => {\n      if (value === 2) throw new Error()\n      return value++\n    }\n    let mutate\n\n    function Page() {\n      const [paused, setPaused] = useState(false)\n      const {\n        data,\n        error,\n        mutate: _mutate\n      } = useSWR(key, fetcher, {\n        revalidateOnMount: true,\n        refreshInterval: 1,\n        isPaused() {\n          return paused\n        }\n      })\n      mutate = _mutate\n\n      useEffect(() => {\n        // revalidate on the mount and turn to idle\n        setPaused(true)\n      }, [])\n\n      return (\n        <div onClick={() => setPaused(!paused)}>\n          {error ? error : `data: ${data}`}\n        </div>\n      )\n    }\n\n    renderWithConfig(<Page />)\n    await screen.findByText('data: 0')\n\n    // should not be revalidated\n    await act(() => mutate())\n    screen.getByText('data: 0')\n    await act(() => mutate())\n    screen.getByText('data: 0')\n\n    // enable isPaused\n    fireEvent.click(screen.getByText('data: 0'))\n    // should be revalidated\n    await act(() => mutate())\n    screen.getByText('data: 1')\n\n    // disable isPaused\n    fireEvent.click(screen.getByText('data: 1'))\n    // should not be revalidated\n    await act(() => mutate())\n    screen.getByText('data: 1')\n    await act(() => mutate())\n    screen.getByText('data: 1')\n  })\n\n  it('should skip initial revalidation when isPaused is true and revalidateOnMount is true', async () => {\n    const key = createKey()\n    let value = 0\n    const fetcher = async () => {\n      await sleep(50)\n      return value++\n    }\n    function Page() {\n      const [paused, setPaused] = useState(false)\n      const { data, isLoading, isValidating } = useSWR(key, fetcher, {\n        revalidateOnMount: true,\n        refreshInterval: 1,\n        isPaused() {\n          return true\n        }\n      })\n      return (\n        <div onClick={() => setPaused(!paused)}>\n          {`data: ${data} | isLoading: ${\n            isLoading ? 'yes' : 'no'\n          } | isValidating: ${isValidating ? 'yes' : 'no'}`}\n        </div>\n      )\n    }\n    renderWithConfig(<Page />)\n    // should not revalidate on mount\n    screen.getByText('data: undefined | isLoading: no | isValidating: no')\n  })\n\n  it('should expose default config as static property on SWRConfig', () => {\n    expect(SWRConfig.defaultValue).toBeDefined()\n  })\n\n  it('should expose the default config from useSWRConfig', () => {\n    let config\n\n    function Page() {\n      config = useSWRConfig()\n      return null\n    }\n\n    renderWithGlobalCache(<Page />)\n    expect(SWRConfig.defaultValue).toEqual(config)\n  })\n\n  it('should expose the correctly extended config from useSWRConfig', () => {\n    let config\n\n    function Page() {\n      config = useSWRConfig()\n      return null\n    }\n\n    const middleware1: Middleware = useSWRNext => (k, f, c) =>\n      useSWRNext(k, f, c)\n    const middleware2: Middleware = useSWRNext => (k, f, c) =>\n      useSWRNext(k, f, c)\n\n    renderWithConfig(\n      <SWRConfig\n        value={{\n          dedupingInterval: 1,\n          refreshInterval: 1,\n          fallback: { a: 1, b: 1 },\n          use: [middleware1]\n        }}\n      >\n        <SWRConfig\n          value={{\n            dedupingInterval: 2,\n            fallback: { a: 2, c: 2 },\n            use: [middleware2]\n          }}\n        >\n          <Page />\n        </SWRConfig>\n      </SWRConfig>\n    )\n\n    expect(config.dedupingInterval).toEqual(2)\n    expect(config.refreshInterval).toEqual(1)\n    expect(config.fallback).toEqual({ a: 2, b: 1, c: 2 })\n    expect(config.use).toEqual([middleware1, middleware2])\n  })\n\n  it('should ignore parent config when value is functional', async () => {\n    let config\n\n    function Page() {\n      config = useSWRConfig()\n      return null\n    }\n\n    const middleware1: Middleware = useSWRNext => (k, f, c) =>\n      useSWRNext(k, f, c)\n    const middleware2: Middleware = useSWRNext => (k, f, c) =>\n      useSWRNext(k, f, c)\n\n    renderWithConfig(\n      <SWRConfig\n        value={{\n          dedupingInterval: 1,\n          refreshInterval: 1,\n          fallback: { a: 1, b: 1 },\n          use: [middleware1]\n        }}\n      >\n        <SWRConfig\n          value={parentConfig => ({\n            dedupingInterval: 2 + parentConfig.dedupingInterval,\n            fallback: { a: 2, c: 2 },\n            use: [middleware2]\n          })}\n        >\n          <Page />\n        </SWRConfig>\n      </SWRConfig>\n    )\n\n    expect(config.dedupingInterval).toEqual(3)\n    expect(config.refreshInterval).toEqual(undefined)\n    expect(config.fallback).toEqual({ a: 2, c: 2 })\n    expect(config.use).toEqual([middleware2])\n  })\n\n  it('should not occur error when fallback is undefined', async () => {\n    const key = createKey()\n    const fetcher = () => 'data'\n\n    function Page() {\n      const { data } = useSWR(key)\n      return <div>data: {data}</div>\n    }\n\n    renderWithConfig(<Page />, {\n      fetcher,\n      fallback: undefined\n    })\n    // hydration\n    screen.getByText('data:')\n    // mount\n    await screen.findByText('data: data')\n  })\n})\n"
  },
  {
    "path": "test/use-swr-context-config.test.tsx",
    "content": "import { render, screen, fireEvent } from '@testing-library/react'\nimport { act } from 'react'\nimport useSWR, {\n  mutate,\n  SWRConfig,\n  type SWRConfiguration,\n  useSWRConfig\n} from 'swr'\nimport { createKey, createResponse, renderWithGlobalCache } from './utils'\nimport { useCallback, useEffect, useState } from 'react'\n\ndescribe('useSWR - context configs', () => {\n  it('mutate before mount should not block rerender', async () => {\n    const prefetch = () => createResponse('prefetch-data')\n    const fetcher = () => createResponse('data')\n    const key = createKey()\n\n    await act(async () => {\n      await mutate(key, prefetch)\n    })\n\n    function Page() {\n      const { data } = useSWR(key, fetcher)\n      return <div>{data}</div>\n    }\n\n    renderWithGlobalCache(<Page />)\n    // render with the prefetched data\n    screen.getByText('prefetch-data')\n\n    // render the fetched data\n    await screen.findByText('data')\n  })\n})\n\ndescribe('useSWRConfig hook maintains stable reference across re-renders', () => {\n  it('should maintain the same swrConfig reference when counter updates', () => {\n    const parentConfig: SWRConfiguration = {\n      revalidateOnMount: true,\n      revalidateIfStale: false,\n      revalidateOnFocus: false,\n      revalidateOnReconnect: false\n    }\n    const counterButtonText = 'counter + 1'\n    let useSWRConfigReferenceChangedTimes = 0\n    function Page() {\n      return (\n        <SWRConfig value={parentConfig}>\n          <ChildComponent />\n        </SWRConfig>\n      )\n    }\n    function ChildComponent() {\n      const swrConfig = useSWRConfig()\n      const [, setCounter] = useState(0)\n      const counterAddOne = useCallback(\n        () => setCounter(prev => prev + 1),\n        [setCounter]\n      )\n      useEffect(() => {\n        useSWRConfigReferenceChangedTimes += 1\n      }, [swrConfig])\n      return <button onClick={counterAddOne}>{counterButtonText}</button>\n    }\n    render(<Page />)\n    fireEvent.click(screen.getByText(counterButtonText))\n    fireEvent.click(screen.getByText(counterButtonText))\n    fireEvent.click(screen.getByText(counterButtonText))\n    expect(useSWRConfigReferenceChangedTimes).toBe(1)\n  })\n})\n"
  },
  {
    "path": "test/use-swr-devtools.test.tsx",
    "content": "import { screen } from '@testing-library/react'\nimport React from 'react'\n\ndescribe('useSWR - devtools', () => {\n  let useSWR, createKey, createResponse, renderWithConfig\n  beforeEach(() => {\n    const middleware =\n      useSWRNext =>\n      (...args) => {\n        const result = useSWRNext(...args)\n        return { ...result, data: 'middleware' }\n      }\n    // @ts-expect-error\n    window.__SWR_DEVTOOLS_USE__ = [middleware]\n    // eslint-disable-next-line @typescript-eslint/no-require-imports\n    ;({ createKey, createResponse, renderWithConfig } = require('./utils'))\n    // eslint-disable-next-line @typescript-eslint/no-require-imports\n    useSWR = require('swr').default\n  })\n  it('window.__SWR_DEVTOOLS_USE__ should be set as middleware', async () => {\n    const key = createKey()\n    function Page() {\n      const { data } = useSWR(key, () => createResponse('ok'))\n      return <div>data: {data}</div>\n    }\n    renderWithConfig(<Page />)\n    await screen.findByText('data: middleware')\n  })\n  it('window.__SWR_DEVTOOLS_REACT__ should be the same reference with React', () => {\n    // @ts-expect-error\n    expect(window.__SWR_DEVTOOLS_REACT__).toBe(React)\n  })\n})\n"
  },
  {
    "path": "test/use-swr-error.test.tsx",
    "content": "import { fireEvent, screen } from '@testing-library/react'\nimport { useEffect, useState, act } from 'react'\nimport useSWR from 'swr'\nimport {\n  sleep,\n  createResponse,\n  createKey,\n  renderWithConfig,\n  mockVisibilityHidden\n} from './utils'\n\ndescribe('useSWR - error', () => {\n  it('should handle errors', async () => {\n    const key = createKey()\n    function Page() {\n      const { data, error } = useSWR(key, () =>\n        createResponse(new Error('error!'))\n      )\n      if (error) return <div>{error.message}</div>\n      return (\n        <div>\n          <>hello, {data}</>\n        </div>\n      )\n    }\n\n    renderWithConfig(<Page />)\n    screen.getByText('hello,')\n\n    // mount\n    await screen.findByText('error!')\n  })\n\n  it('should trigger the onError event', async () => {\n    const key = createKey()\n    let erroredSWR = null\n    function Page() {\n      const { data, error } = useSWR(\n        key,\n        () => createResponse(new Error('error!')),\n        { onError: (_, errorKey) => (erroredSWR = errorKey) }\n      )\n      if (error) return <div>{error.message}</div>\n      return (\n        <div>\n          <>hello, {data}</>\n        </div>\n      )\n    }\n\n    renderWithConfig(<Page />)\n    screen.getByText('hello,')\n\n    // mount\n    await screen.findByText('error!')\n    expect(erroredSWR).toEqual(key)\n  })\n\n  it('should trigger error retry', async () => {\n    const key = createKey()\n    let count = 0\n    function Page() {\n      const { data, error } = useSWR(\n        key,\n        () => createResponse(new Error('error: ' + count++), { delay: 100 }),\n        {\n          onErrorRetry: (_, __, ___, revalidate, revalidateOpts) => {\n            setTimeout(() => revalidate(revalidateOpts), 50)\n          },\n          dedupingInterval: 0\n        }\n      )\n      if (error) return <div>{error.message}</div>\n      return (\n        <div>\n          <>hello, {data}</>\n        </div>\n      )\n    }\n    renderWithConfig(<Page />)\n    screen.getByText('hello,')\n\n    // mount\n    await screen.findByText('error: 0')\n\n    await act(() => sleep(200)) // retry\n    screen.getByText('error: 1')\n\n    await act(() => sleep(200)) // retry\n    screen.getByText('error: 2')\n  })\n\n  it('should stop retrying when document is not visible', async () => {\n    const key = createKey()\n    let count = 0\n    function Page() {\n      const { data, error } = useSWR(\n        key,\n        () => createResponse(new Error('error: ' + count++), { delay: 100 }),\n        {\n          onErrorRetry: (_, __, ___, revalidate, revalidateOpts) => {\n            revalidate(revalidateOpts)\n          },\n          dedupingInterval: 0\n        }\n      )\n      if (error) return <div>{error.message}</div>\n      return (\n        <div>\n          <>hello, {data}</>\n        </div>\n      )\n    }\n    renderWithConfig(<Page />)\n    screen.getByText('hello,')\n\n    // mount\n    await screen.findByText('error: 0')\n\n    // errored, retrying\n    await act(() => sleep(50))\n    const resetVisibility = mockVisibilityHidden()\n\n    await act(() => sleep(100))\n    screen.getByText('error: 1')\n\n    await act(() => sleep(100)) // stopped due to invisible\n    screen.getByText('error: 1')\n\n    resetVisibility()\n  })\n\n  it('should not retry when shouldRetryOnError is disabled', async () => {\n    const key = createKey()\n    let count = 0\n    function Page() {\n      const { data, error } = useSWR(\n        key,\n        () => createResponse(new Error('error: ' + count++), { delay: 100 }),\n        {\n          onErrorRetry: (_, __, ___, revalidate, revalidateOpts) => {\n            revalidate(revalidateOpts)\n          },\n          dedupingInterval: 0,\n          shouldRetryOnError: false\n        }\n      )\n      if (error) return <div>{error.message}</div>\n      return (\n        <div>\n          <>hello, {data}</>\n        </div>\n      )\n    }\n    renderWithConfig(<Page />)\n    screen.getByText('hello,')\n\n    // mount\n    await screen.findByText('error: 0')\n\n    await act(() => sleep(150))\n    screen.getByText('error: 0')\n  })\n\n  it('should not retry when shouldRetryOnError function returns false', async () => {\n    const key = createKey()\n    let count = 0\n    function Page() {\n      const { data, error } = useSWR(\n        key,\n        () => createResponse(new Error('error: ' + count++), { delay: 100 }),\n        {\n          onErrorRetry: (_, __, ___, revalidate, revalidateOpts) => {\n            revalidate(revalidateOpts)\n          },\n          dedupingInterval: 0,\n          shouldRetryOnError: () => false\n        }\n      )\n      if (error) return <div>{error.message}</div>\n      return (\n        <div>\n          <>hello, {data}</>\n        </div>\n      )\n    }\n    renderWithConfig(<Page />)\n    screen.getByText('hello,')\n\n    // mount\n    await screen.findByText('error: 0')\n\n    await act(() => sleep(150))\n    screen.getByText('error: 0')\n  })\n\n  it('should retry when shouldRetryOnError function returns true', async () => {\n    const key = createKey()\n    let count = 0\n    function Page() {\n      const { data, error } = useSWR(\n        key,\n        () => createResponse(new Error('error: ' + count++), { delay: 100 }),\n        {\n          onErrorRetry: (_, __, ___, revalidate, revalidateOpts) => {\n            revalidate(revalidateOpts)\n          },\n          dedupingInterval: 0,\n          shouldRetryOnError: () => true\n        }\n      )\n      if (error) return <div>{error.message}</div>\n      return (\n        <div>\n          <>hello, {data}</>\n        </div>\n      )\n    }\n    renderWithConfig(<Page />)\n    screen.getByText('hello,')\n\n    // mount\n    await screen.findByText('error: 0')\n\n    await act(() => sleep(150))\n    screen.getByText('error: 1')\n  })\n\n  it('should trigger the onLoadingSlow and onSuccess event', async () => {\n    const key = createKey()\n    let loadingSlow = null,\n      success = null\n    function Page() {\n      const { data } = useSWR(\n        key,\n        () => createResponse('SWR', { delay: 200 }),\n        {\n          onLoadingSlow: loadingKey => (loadingSlow = loadingKey),\n          onSuccess: (_, successKey) => (success = successKey),\n          loadingTimeout: 100\n        }\n      )\n      return <div>hello, {data}</div>\n    }\n\n    renderWithConfig(<Page />)\n    screen.getByText('hello,')\n    expect(loadingSlow).toEqual(null)\n\n    await act(() => sleep(150)) // trigger onLoadingSlow event\n    expect(loadingSlow).toEqual(key)\n    expect(success).toEqual(null)\n\n    await act(() => sleep(150)) // finish the request\n    expect(success).toEqual(key)\n    screen.getByText('hello, SWR')\n  })\n  it('should trigger limited error retries if errorRetryCount exists', async () => {\n    const key = createKey()\n    let count = 0\n    function Page() {\n      const { data, error } = useSWR(\n        key,\n        () => createResponse(new Error('error: ' + count++)),\n        {\n          errorRetryCount: 1,\n          errorRetryInterval: 50,\n          dedupingInterval: 0\n        }\n      )\n      if (error) return <div>{error.message}</div>\n      return (\n        <div>\n          <>hello, {data}</>\n        </div>\n      )\n    }\n\n    renderWithConfig(<Page />)\n    screen.getByText('hello,')\n\n    // mount\n    await screen.findByText('error: 0')\n\n    await screen.findByText('error: 1') // retry\n\n    await act(() => sleep(200)) // a retry request won't happen because retryCount is over\n    screen.getByText('error: 1')\n  })\n\n  it.skip('should not trigger the onLoadingSlow and onSuccess event after component unmount', async () => {\n    const key = createKey()\n    let loadingSlow = null,\n      success = null\n    function Page() {\n      const { data } = useSWR(key, () => createResponse('SWR'), {\n        onLoadingSlow: loadingKey => {\n          loadingSlow = loadingKey\n        },\n        onSuccess: (_, successKey) => {\n          success = successKey\n        },\n        loadingTimeout: 100\n      })\n      return <div>hello, {data}</div>\n    }\n\n    function App() {\n      const [on, toggle] = useState(true)\n      return (\n        <div id=\"app\" onClick={() => toggle(s => !s)}>\n          {on && <Page />}\n        </div>\n      )\n    }\n\n    renderWithConfig(<App />)\n    screen.getByText('hello,')\n    expect(loadingSlow).toEqual(null)\n    expect(success).toEqual(null)\n\n    fireEvent.click(screen.getByText('hello,'))\n    await act(() => sleep(200))\n    expect(success).toEqual(null)\n    expect(loadingSlow).toEqual(null)\n  })\n\n  it.skip('should not trigger the onError and onErrorRetry event after component unmount', async () => {\n    const key = createKey()\n    let retry = null,\n      failed = null\n    function Page() {\n      const { data } = useSWR(key, () => createResponse(new Error('error!')), {\n        onError: (_, errorKey) => {\n          failed = errorKey\n        },\n        onErrorRetry: (_, errorKey) => {\n          retry = errorKey\n        },\n        dedupingInterval: 0\n      })\n      return (\n        <div>\n          <>hello, {data}</>\n        </div>\n      )\n    }\n\n    function App() {\n      const [on, toggle] = useState(true)\n      return (\n        <div id=\"app\" onClick={() => toggle(s => !s)}>\n          {on && <Page />}\n        </div>\n      )\n    }\n\n    renderWithConfig(<App />)\n    screen.getByText('hello,')\n    expect(retry).toEqual(null)\n    expect(failed).toEqual(null)\n\n    fireEvent.click(screen.getByText('hello,'))\n    await act(() => sleep(200))\n    expect(retry).toEqual(null)\n    expect(failed).toEqual(null)\n  })\n\n  it('should not trigger error retries if errorRetryCount is set to 0', async () => {\n    const key = createKey()\n    let count = 0\n    function Page() {\n      const { data, error } = useSWR(\n        key,\n        () => createResponse(new Error('error: ' + count++)),\n        {\n          errorRetryCount: 0,\n          errorRetryInterval: 50,\n          dedupingInterval: 0\n        }\n      )\n      if (error) return <div>{error.message}</div>\n      return (\n        <div>\n          <>hello, {data}</>\n        </div>\n      )\n    }\n\n    renderWithConfig(<Page />)\n    screen.getByText('hello,')\n\n    // mount\n    await screen.findByText('error: 0')\n\n    await act(() => sleep(210)) // retry is never happen\n    screen.getByText('error: 0')\n  })\n\n  it('should not clear error during revalidating until fetcher is finished successfully', async () => {\n    const errors = []\n    const key = createKey()\n    let mutate\n    function Page() {\n      const { error, mutate: _mutate } = useSWR(\n        key,\n        () => createResponse(new Error('error')),\n        {\n          errorRetryCount: 0,\n          errorRetryInterval: 0,\n          dedupingInterval: 0\n        }\n      )\n      mutate = _mutate\n      useEffect(() => {\n        errors.push(error ? error.message : null)\n      }, [error])\n\n      return <div>hello, {error ? error.message : null}</div>\n    }\n\n    renderWithConfig(<Page />)\n\n    // mount\n    await screen.findByText('hello, error')\n\n    await act(() => mutate())\n    // initial -> first error -> mutate -> receive another error\n    // the error won't be cleared during revalidation\n    expect(errors).toEqual([null, 'error', 'error'])\n  })\n\n  it('should reset isValidating when an error occured synchronously', async () => {\n    const key = createKey()\n    function Page() {\n      const { error, isValidating } = useSWR(key, () => {\n        throw new Error('error!')\n      })\n      if (error)\n        return (\n          <div>\n            {error.message},{isValidating.toString()}\n          </div>\n        )\n      return <div>hello,{isValidating.toString()}</div>\n    }\n\n    renderWithConfig(<Page />)\n    screen.getByText('error!,false')\n  })\n\n  it('should reset isValidating when an error occured asynchronously', async () => {\n    const key = createKey()\n    function Page() {\n      const { error, isValidating } = useSWR(key, () =>\n        createResponse(new Error('error!'))\n      )\n      if (error)\n        return (\n          <div>\n            {error.message},{isValidating.toString()}\n          </div>\n        )\n      return <div>hello,{isValidating.toString()}</div>\n    }\n\n    renderWithConfig(<Page />)\n    screen.getByText('hello,true')\n\n    await screen.findByText('error!,false')\n  })\n\n  it('should dedupe onError events', async () => {\n    const key = createKey()\n    const errorEvents = []\n    function Foo() {\n      useSWR(key, () => createResponse(new Error('error!'), { delay: 20 }), {\n        onError: e => errorEvents.push(e)\n      })\n      return null\n    }\n    function Page() {\n      return (\n        <>\n          <Foo />\n          <Foo />\n        </>\n      )\n    }\n\n    renderWithConfig(<Page />)\n    await act(() => sleep(30))\n\n    // Since there's only 1 request fired, only 1 error event should be reported.\n    expect(errorEvents.length).toBe(1)\n  })\n\n  it('should not trigger revalidation when a key is already active and has error - isLoading', async () => {\n    const key = createKey()\n    const useData = () => {\n      return useSWR<any, any>(key, () =>\n        createResponse(new Error('error!'), { delay: 200 })\n      )\n    }\n    const Page = () => {\n      useData()\n      return null\n    }\n    const App = () => {\n      const { error, isLoading } = useData()\n      if (isLoading) return <span>loading</span>\n      return (\n        <div>\n          {error ? error.message : ''},{isLoading.toString()}\n          <Page />\n        </div>\n      )\n    }\n    renderWithConfig(<App />)\n    screen.getByText('loading')\n    await screen.findByText('error!,false')\n  })\n\n  it('should not trigger revalidation when a key is already active and has error - isValidating', async () => {\n    const key = createKey()\n    const useData = () => {\n      return useSWR<any, any>(key, () =>\n        createResponse(new Error('error!'), { delay: 200 })\n      )\n    }\n    const Page = () => {\n      useData()\n      return null\n    }\n    const App = () => {\n      const { error, isValidating } = useData()\n      if (isValidating) return <span>validating</span>\n      return (\n        <div>\n          {error ? error.message : ''},{isValidating.toString()}\n          <Page />\n        </div>\n      )\n    }\n    renderWithConfig(<App />)\n    screen.getByText('validating')\n    await screen.findByText('error!,false')\n  })\n\n  it('should trigger revalidation when first hook is unmount', async () => {\n    const key = createKey()\n    const fn = jest.fn()\n    const useData = () => {\n      return useSWR<any, any>(\n        key,\n        () => createResponse(new Error('error!'), { delay: 200 }),\n        {\n          onErrorRetry: (_, __, ___, revalidate, opts) => {\n            setTimeout(() => {\n              fn()\n              revalidate(opts)\n            }, 100)\n          },\n          dedupingInterval: 1\n        }\n      )\n    }\n    const First = () => {\n      useData()\n      const [state, setState] = useState(true)\n      return (\n        <>\n          <button onClick={() => setState(!state)}>toggle</button>\n          {state ? <Second></Second> : null}\n        </>\n      )\n    }\n    const Second = () => {\n      useData()\n      return null\n    }\n    const App = () => {\n      return (\n        <div>\n          <First></First>\n          <Second />\n        </div>\n      )\n    }\n    renderWithConfig(<App />)\n    await act(() => sleep(320))\n    expect(fn).toHaveBeenCalledTimes(1)\n    fireEvent.click(screen.getByText('toggle'))\n    await act(() => sleep(320))\n    expect(fn).toHaveBeenCalledTimes(2)\n    await act(() => sleep(320))\n    expect(fn).toHaveBeenCalledTimes(3)\n  })\n})\n"
  },
  {
    "path": "test/use-swr-fetcher.test.tsx",
    "content": "import { fireEvent, screen } from '@testing-library/react'\nimport { Suspense, useState, act } from 'react'\nimport useSWR from 'swr'\nimport {\n  createKey,\n  renderWithConfig,\n  nextTick,\n  itShouldSkipForReactCanary\n} from './utils'\n\ndescribe('useSWR - fetcher', () => {\n  // https://github.com/vercel/swr/issues/1131\n  it('should use the latest fetcher reference', async () => {\n    const key = createKey()\n    let fetcher = () => 'foo'\n    let mutate\n    let rerender\n\n    function Page() {\n      const { data, mutate: boundMutate } = useSWR(key, fetcher)\n      rerender = useState({})[1]\n      mutate = boundMutate\n\n      return <div>data:{data}</div>\n    }\n\n    renderWithConfig(<Page />)\n    await nextTick()\n    screen.getByText('data:foo')\n\n    // Change the fetcher and make sure the ref is updated.\n    fetcher = () => 'bar'\n    act(() => rerender({}))\n\n    // Revalidate.\n    await act(() => mutate())\n\n    // Should fetch with the new fetcher.\n    await screen.findByText('data:bar')\n  })\n\n  it('should use the latest fetcher reference when the key has been changed', async () => {\n    const key = createKey()\n    let fetcher = () => 'foo'\n\n    function Page() {\n      const [prefix, setPrefix] = useState('a')\n      const { data } = useSWR(prefix + key, fetcher)\n\n      return (\n        <div>\n          <p>data:{data}</p>\n          <button\n            onClick={() => {\n              setPrefix('b')\n            }}\n          >\n            mutate\n          </button>\n        </div>\n      )\n    }\n\n    renderWithConfig(<Page />)\n    await screen.findByText('data:foo')\n\n    // Change the fetcher and make sure the ref is updated.\n    fetcher = () => 'bar'\n    fireEvent.click(screen.getByText('mutate'))\n\n    // Should fetch with the new fetcher.\n    await screen.findByText('data:bar')\n  })\n\n  itShouldSkipForReactCanary(\n    'should use the latest fetcher reference with the suspense mode when the key has been changed',\n    async () => {\n      const key = createKey()\n      let fetcher = () => 'foo'\n\n      function Page() {\n        const [prefix, setPrefix] = useState('a')\n        const { data } = useSWR(prefix + key, fetcher, { suspense: true })\n\n        return (\n          <div>\n            <p>data:{data}</p>\n            <button\n              onClick={() => {\n                setPrefix('b')\n              }}\n            >\n              mutate\n            </button>\n          </div>\n        )\n      }\n\n      renderWithConfig(\n        <Suspense fallback=\"loading\">\n          <Page />\n        </Suspense>\n      )\n      await screen.findByText('data:foo')\n\n      // Change the fetcher and make sure the ref is updated.\n      fetcher = () => 'bar'\n      fireEvent.click(screen.getByText('mutate'))\n\n      // Should fetch with the new fetcher.\n      await screen.findByText('data:bar')\n    }\n  )\n\n  it('should be able to pass falsy values to the fetcher', () => {\n    const key = createKey()\n\n    function Page({ fetcher }) {\n      const { data } = useSWR(key, fetcher)\n\n      return (\n        <div>\n          <p>data:{data}</p>\n        </div>\n      )\n    }\n\n    const { rerender } = renderWithConfig(<Page fetcher={null} />)\n    screen.getByText('data:')\n\n    rerender(<Page fetcher={undefined} />)\n    screen.getByText('data:')\n\n    rerender(<Page fetcher={false} />)\n    screen.getByText('data:')\n  })\n})\n"
  },
  {
    "path": "test/use-swr-focus.test.tsx",
    "content": "import { fireEvent, screen } from '@testing-library/react'\nimport { useState, act } from 'react'\nimport useSWR from 'swr'\nimport {\n  sleep,\n  nextTick as waitForNextTick,\n  focusOn,\n  renderWithConfig,\n  createKey\n} from './utils'\n\nconst focusWindow = () => focusOn(window)\n\ndescribe('useSWR - focus', () => {\n  it('should revalidate on focus by default', async () => {\n    let value = 0\n    const key = createKey()\n    function Page() {\n      const { data } = useSWR(key, () => value++, {\n        dedupingInterval: 0,\n        focusThrottleInterval: 0\n      })\n      return <div>data: {data}</div>\n    }\n\n    renderWithConfig(<Page />)\n    // hydration\n    screen.getByText('data:')\n    // mount\n    await screen.findByText('data: 0')\n\n    await waitForNextTick()\n    // trigger revalidation\n    await focusWindow()\n\n    await screen.findByText('data: 1')\n  })\n\n  it(\"shouldn't revalidate on focus when revalidateOnFocus is false\", async () => {\n    let value = 0\n\n    const key = createKey()\n    function Page() {\n      const { data } = useSWR(key, () => value++, {\n        dedupingInterval: 0,\n        revalidateOnFocus: false\n      })\n      return <div>data: {data}</div>\n    }\n\n    renderWithConfig(<Page />)\n    // hydration\n    screen.getByText('data:')\n\n    // mount\n    await screen.findByText('data: 0')\n\n    await waitForNextTick()\n    // trigger revalidation\n    await focusWindow()\n    // should not be revalidated\n    screen.getByText('data: 0')\n  })\n\n  it('revalidateOnFocus should be stateful', async () => {\n    let value = 0\n\n    const key = createKey()\n    function Page() {\n      const [revalidateOnFocus, toggle] = useState(false)\n      const { data } = useSWR(key, () => value++, {\n        dedupingInterval: 0,\n        revalidateOnFocus,\n        focusThrottleInterval: 0\n      })\n      return <div onClick={() => toggle(s => !s)}>data: {data}</div>\n    }\n\n    renderWithConfig(<Page />)\n    // hydration\n    screen.getByText('data:')\n\n    // mount\n    await screen.findByText('data: 0')\n\n    await waitForNextTick()\n    // trigger revalidation\n    await focusWindow()\n    // data should not change\n    screen.getByText('data: 0')\n\n    // change revalidateOnFocus to true\n    fireEvent.click(screen.getByText('data: 0'))\n    // trigger revalidation\n    await focusWindow()\n    // data should update\n    await screen.findByText('data: 1')\n\n    await waitForNextTick()\n    // trigger revalidation\n    await focusWindow()\n    // data should update\n    await screen.findByText('data: 2')\n\n    await waitForNextTick()\n    // change revalidateOnFocus to false\n    fireEvent.click(screen.getByText('data: 2'))\n    // trigger revalidation\n    await focusWindow()\n    // data should not change\n    screen.getByText('data: 2')\n  })\n\n  it('focusThrottleInterval should work', async () => {\n    let value = 0\n\n    const key = createKey()\n    function Page() {\n      const { data } = useSWR(key, () => value++, {\n        dedupingInterval: 0,\n        revalidateOnFocus: true,\n        focusThrottleInterval: 50\n      })\n      return <div>data: {data}</div>\n    }\n\n    renderWithConfig(<Page />)\n    // hydration\n    screen.getByText('data:')\n\n    // mount\n    await screen.findByText('data: 0')\n\n    await waitForNextTick()\n    // still in throttling interval\n    await act(() => sleep(20))\n    // should be throttled\n    await focusWindow()\n    await screen.findByText('data: 0')\n    // wait for focusThrottleInterval\n    await act(() => sleep(100))\n\n    // trigger revalidation again\n    await focusWindow()\n    await screen.findByText('data: 1')\n  })\n\n  it('focusThrottleInterval should be stateful', async () => {\n    let value = 0\n\n    const key = createKey()\n    function Page() {\n      const [focusThrottleInterval, setInterval] = useState(50)\n      const { data } = useSWR(key, () => value++, {\n        dedupingInterval: 0,\n        revalidateOnFocus: true,\n        focusThrottleInterval\n      })\n      return <div onClick={() => setInterval(s => s + 100)}>data: {data}</div>\n    }\n\n    renderWithConfig(<Page />)\n    // hydration\n    screen.getByText('data:')\n\n    // mount\n    await screen.findByText('data: 0')\n\n    await waitForNextTick()\n    // trigger revalidation, won't revalidate as within 50ms\n    await focusWindow()\n    // wait for throttle interval\n    await act(() => sleep(100))\n    // trigger revalidation, will revalidate as 50ms passed\n    await focusWindow()\n    await screen.findByText('data: 1')\n\n    await waitForNextTick()\n    // increase focusThrottleInterval\n    fireEvent.click(screen.getByText('data: 1'))\n    // wait for throttle interval\n    await act(() => sleep(100))\n    // trigger revalidation\n    await focusWindow()\n    // wait for throttle interval\n    await act(() => sleep(100))\n    // should be throttled\n    await focusWindow()\n    await screen.findByText('data: 2')\n\n    // wait for throttle interval\n    await act(() => sleep(150))\n    // trigger revalidation\n    await focusWindow()\n    // wait for throttle intervals\n    await act(() => sleep(150))\n    await screen.findByText('data: 3')\n  })\n\n  it('should revalidate on focus even with custom cache', async () => {\n    let value = 0\n\n    const key = createKey()\n    function Page() {\n      const { data } = useSWR(key, () => value++, {\n        revalidateOnFocus: true,\n        dedupingInterval: 0,\n        focusThrottleInterval: 0\n      })\n      return <div>data: {data}</div>\n    }\n\n    renderWithConfig(<Page />)\n    screen.getByText('data:')\n    await screen.findByText('data: 0')\n    await waitForNextTick()\n    await focusWindow()\n    await screen.findByText('data: 1')\n  })\n\n  it('should not revalidate on focus when key changes in the same tick', async () => {\n    const fetchLogs = []\n\n    function Page() {\n      const [key, setKey] = useState(() => createKey())\n      useSWR(key, k => fetchLogs.push(k), {\n        revalidateOnFocus: true,\n        dedupingInterval: 0\n      })\n      return <div onClick={() => setKey(createKey())}>change key</div>\n    }\n\n    renderWithConfig(<Page />)\n    await waitForNextTick()\n\n    fireEvent.focus(window)\n    fireEvent.click(screen.getByText('change key'))\n\n    await waitForNextTick()\n\n    // Only fetched twice with the initial and the new keys.\n    expect(fetchLogs.length).toBe(2)\n    expect(fetchLogs[0]).not.toEqual(fetchLogs[1])\n  })\n})\n"
  },
  {
    "path": "test/use-swr-immutable.test.tsx",
    "content": "import { screen, fireEvent } from '@testing-library/react'\nimport { useState, act, Profiler } from 'react'\nimport useSWR, { SWRConfig } from 'swr'\nimport useSWRImmutable, { immutable } from 'swr/immutable'\nimport {\n  sleep,\n  createKey,\n  nextTick as waitForNextTick,\n  focusOn,\n  renderWithConfig,\n  createResponse\n} from './utils'\n\nconst focusWindow = () => focusOn(window)\n\ndescribe('useSWR - immutable', () => {\n  it('should revalidate on mount', async () => {\n    let value = 0\n    const key = createKey()\n    const useData = () =>\n      useSWR(key, () => value++, {\n        dedupingInterval: 0\n      })\n\n    function Component() {\n      useData()\n      return null\n    }\n\n    function Page() {\n      const [showComponent, setShowComponent] = useState(false)\n      const { data } = useData()\n      return (\n        <div>\n          <button onClick={() => setShowComponent(true)}>\n            mount component\n          </button>\n          <p>data: {data}</p>\n          {showComponent ? <Component /> : null}\n        </div>\n      )\n    }\n\n    renderWithConfig(<Page />)\n\n    // hydration\n    screen.getByText('mount component')\n    screen.getByText('data:')\n\n    // ready\n    await screen.findByText('data: 0')\n\n    // mount <Component/> by clicking the button\n    fireEvent.click(screen.getByText('mount component'))\n\n    // wait for rerender\n    await screen.findByText('data: 1')\n  })\n\n  it('should not revalidate on mount when `revalidateIfStale` is enabled', async () => {\n    let value = 0\n    const key = createKey()\n    const useData = () =>\n      useSWR(key, () => value++, {\n        dedupingInterval: 0,\n        revalidateIfStale: false\n      })\n\n    function Component() {\n      useData()\n      return null\n    }\n    function Page() {\n      const [showComponent, setShowComponent] = useState(false)\n      const { data } = useData()\n      return (\n        <div>\n          <button onClick={() => setShowComponent(true)}>\n            mount component\n          </button>\n          <p>data: {data}</p>\n          {showComponent ? <Component /> : null}\n        </div>\n      )\n    }\n\n    renderWithConfig(<Page />)\n\n    // hydration\n    screen.getByText('mount component')\n    screen.getByText('data:')\n\n    // ready\n    await screen.findByText('data: 0')\n\n    // mount <Component/> by clicking the button\n    fireEvent.click(screen.getByText('mount component'))\n\n    // wait for rerender\n    await act(() => sleep(50))\n    await screen.findByText('data: 0')\n  })\n\n  it('should not revalidate with the immutable hook', async () => {\n    let value = 0\n    const key = createKey()\n    const useData = () =>\n      useSWRImmutable(key, () => value++, { dedupingInterval: 0 })\n\n    function Component() {\n      useData()\n      return null\n    }\n    function Page() {\n      const [showComponent, setShowComponent] = useState(false)\n      const { data } = useData()\n      return (\n        <div>\n          <button onClick={() => setShowComponent(true)}>\n            mount component\n          </button>\n          <p>data: {data}</p>\n          {showComponent ? <Component /> : null}\n        </div>\n      )\n    }\n\n    renderWithConfig(<Page />)\n\n    // hydration\n    screen.getByText('mount component')\n    screen.getByText('data:')\n\n    // ready\n    await screen.findByText('data: 0')\n\n    // mount <Component/> by clicking the button\n    fireEvent.click(screen.getByText('mount component'))\n\n    // trigger window focus\n    await waitForNextTick()\n    await focusWindow()\n\n    // wait for rerender\n    await act(() => sleep(50))\n    await screen.findByText('data: 0')\n  })\n\n  it('should not revalidate with the immutable middleware', async () => {\n    let value = 0\n    const key = createKey()\n    const useData = () =>\n      useSWR(key, () => value++, {\n        dedupingInterval: 0,\n        use: [immutable]\n      })\n\n    function Component() {\n      useData()\n      return null\n    }\n    function Page() {\n      const [showComponent, setShowComponent] = useState(false)\n      const { data } = useData()\n      return (\n        <div>\n          <button onClick={() => setShowComponent(true)}>\n            mount component\n          </button>\n          <p>data: {data}</p>\n          {showComponent ? <Component /> : null}\n        </div>\n      )\n    }\n\n    renderWithConfig(<Page />)\n\n    // hydration\n    screen.getByText('mount component')\n    screen.getByText('data:')\n\n    // ready\n    await screen.findByText('data: 0')\n\n    // mount <Component/> by clicking the button\n    fireEvent.click(screen.getByText('mount component'))\n\n    // trigger window focus\n    await waitForNextTick()\n    await focusWindow()\n\n    // wait for rerender\n    await act(() => sleep(50))\n    await screen.findByText('data: 0')\n  })\n\n  it('should not revalidate with revalidateIfStale disabled when key changes', async () => {\n    const fetcher = jest.fn(v => v)\n\n    const key = createKey()\n    const useData = (id: string) =>\n      useSWR(key + id, fetcher, {\n        dedupingInterval: 0,\n        revalidateIfStale: false\n      })\n\n    function Page() {\n      const [id, setId] = useState('0')\n      const { data } = useData(id)\n      return (\n        <button onClick={() => setId(id === '0' ? '1' : '0')}>\n          data: {data}\n        </button>\n      )\n    }\n\n    renderWithConfig(<Page />)\n\n    // Ready\n    await screen.findByText(`data: ${key}0`)\n\n    // Toggle the key by clicking the button\n    fireEvent.click(screen.getByText(`data: ${key}0`))\n    await screen.findByText(`data: ${key}1`)\n\n    await waitForNextTick()\n\n    // Toggle the key again by clicking the button\n    fireEvent.click(screen.getByText(`data: ${key}1`))\n    await screen.findByText(`data: ${key}0`)\n\n    await waitForNextTick()\n\n    // Toggle the key by clicking the button\n    fireEvent.click(screen.getByText(`data: ${key}0`))\n    await screen.findByText(`data: ${key}1`)\n\n    await sleep(20)\n\n    // `fetcher` should only be called twice, with each key.\n    expect(fetcher).toHaveBeenCalledTimes(2)\n    expect(fetcher).toHaveBeenNthCalledWith(1, key + '0')\n    expect(fetcher).toHaveBeenNthCalledWith(2, key + '1')\n  })\n\n  it('isLoading and isValidating should be true when switch to new key', async () => {\n    const key = createKey()\n    const onRender = jest.fn()\n    const useData = (id: string) =>\n      useSWRImmutable(\n        key + id,\n        () =>\n          createResponse(id, {\n            delay: 100\n          }),\n        {\n          dedupingInterval: 0\n        }\n      )\n\n    function Data() {\n      const [id, setId] = useState('0')\n      const { data, isLoading, isValidating } = useData(id)\n      return (\n        <div>\n          <button onClick={() => setId(id === '0' ? '1' : '0')}>\n            switch id\n          </button>\n          <p>\n            data: {data}, isLoading: {isLoading ? 'true' : 'false'},\n            isValidating: {isValidating ? 'true' : 'false'}\n          </p>\n        </div>\n      )\n    }\n\n    function Page() {\n      return (\n        <Profiler\n          onRender={(id, phase) => {\n            onRender(id, phase)\n          }}\n          id={key}\n        >\n          <Data />\n        </Profiler>\n      )\n    }\n\n    renderWithConfig(<Page />)\n    await screen.findByText(`data: , isLoading: true, isValidating: true`)\n    await screen.findByText(`data: 0, isLoading: false, isValidating: false`)\n    fireEvent.click(screen.getByText('switch id'))\n    await screen.findByText(`data: , isLoading: true, isValidating: true`)\n    await screen.findByText(`data: 1, isLoading: false, isValidating: false`)\n    expect(onRender).toHaveBeenCalledTimes(4)\n  })\n})\n\ndescribe('issue #4207', () => {\n  it('should ignore global refreshInterval', async () => {\n    let fetchCount = 0\n    const fetcher = () => {\n      fetchCount++\n      return 'data'\n    }\n\n    function Page() {\n      const { data } = useSWRImmutable('key', fetcher)\n      return <div>{data}</div>\n    }\n\n    renderWithConfig(\n      <SWRConfig\n        value={{\n          refreshInterval: 100,\n          dedupingInterval: 0,\n          provider: () => new Map()\n        }}\n      >\n        <Page />\n      </SWRConfig>\n    )\n\n    await screen.findByText('data')\n    expect(fetchCount).toBe(1)\n\n    await new Promise(resolve => setTimeout(resolve, 300))\n    expect(fetchCount).toBe(1)\n  })\n})\n"
  },
  {
    "path": "test/use-swr-infinite-preload.test.tsx",
    "content": "import { fireEvent, screen } from '@testing-library/react'\nimport { Suspense, useEffect, useState, Profiler, act } from 'react'\nimport { preload } from 'swr'\nimport useSWRInfinite from 'swr/infinite'\nimport {\n  createKey,\n  createResponse,\n  itShouldSkipForReactCanary,\n  renderWithConfig,\n  sleep\n} from './utils'\n\ndescribe('useSWRInfinite - preload', () => {\n  const getKeyFunction = (key: string) => (index: number) =>\n    `page-${index}-${key}`\n\n  it('preloading useSWRInfinite should produce the same result', async () => {\n    const key = createKey()\n    const getKey = getKeyFunction(key)\n\n    const fetcher = jest.fn(() => createResponse('foo'))\n    function Page() {\n      const { data } = useSWRInfinite(getKey, fetcher)\n      return <div>data:{Array.isArray(data) ? 'true' : 'false'}</div>\n    }\n\n    preload(getKey(0), fetcher)\n    renderWithConfig(<Page />)\n    await screen.findByText('data:true')\n  })\n\n  it('preload the fetcher function', async () => {\n    const key = createKey()\n    const getKey = getKeyFunction(key)\n\n    const fetcher = jest.fn(() => createResponse('foo'))\n    function Page() {\n      const { data } = useSWRInfinite(getKey, fetcher)\n      return <div>data:{data}</div>\n    }\n\n    preload(getKey(0), fetcher)\n    expect(fetcher).toHaveBeenCalledTimes(1)\n\n    renderWithConfig(<Page />)\n    await screen.findByText('data:foo')\n    expect(fetcher).toHaveBeenCalledTimes(1)\n  })\n\n  it('should avoid preloading the resource multiple times', async () => {\n    const key = createKey()\n    const getKey = getKeyFunction(key)\n    const fetcher = jest.fn(() => createResponse('foo'))\n\n    function Page() {\n      const { data } = useSWRInfinite(getKey, fetcher)\n      return <div>data:{data}</div>\n    }\n\n    preload(getKey(0), fetcher)\n    preload(getKey(0), fetcher)\n    preload(getKey(0), fetcher)\n    expect(fetcher).toHaveBeenCalledTimes(1)\n\n    renderWithConfig(<Page />)\n    await screen.findByText('data:foo')\n    expect(fetcher).toHaveBeenCalledTimes(1)\n  })\n\n  it('should be able to prealod resources in effects', async () => {\n    const key = createKey()\n    const getKey = getKeyFunction(key)\n    const fetcher = jest.fn(() => createResponse('foo'))\n\n    function Comp() {\n      const { data } = useSWRInfinite(getKey, fetcher)\n      return <div>data:{data}</div>\n    }\n\n    function Page() {\n      const [show, setShow] = useState(false)\n      useEffect(() => {\n        preload(getKey(0), fetcher)\n      }, [])\n      return show ? (\n        <Comp />\n      ) : (\n        <button onClick={() => setShow(true)}>click</button>\n      )\n    }\n\n    renderWithConfig(<Page />)\n    expect(fetcher).toHaveBeenCalledTimes(1)\n\n    fireEvent.click(screen.getByText('click'))\n\n    await screen.findByText('data:foo')\n    expect(fetcher).toHaveBeenCalledTimes(1)\n  })\n\n  itShouldSkipForReactCanary(\n    'preload the fetcher function with the suspense mode',\n    async () => {\n      const key = createKey()\n      const getKey = getKeyFunction(key)\n      const fetcher = jest.fn(() => createResponse('foo'))\n      const onRender = jest.fn()\n      function Page() {\n        const { data } = useSWRInfinite(getKey, fetcher, { suspense: true })\n        return <div>data:{data}</div>\n      }\n\n      preload(getKey(0), fetcher)\n      expect(fetcher).toHaveBeenCalledTimes(1)\n\n      renderWithConfig(\n        <Suspense\n          fallback={\n            <Profiler id={key} onRender={onRender}>\n              loading\n            </Profiler>\n          }\n        >\n          <Page />\n        </Suspense>\n      )\n      await screen.findByText('data:foo')\n      expect(onRender).toHaveBeenCalledTimes(1)\n      expect(fetcher).toHaveBeenCalledTimes(1)\n    }\n  )\n\n  it.skip('avoid suspense waterfall by prefetching the resources', async () => {\n    const key1 = createKey()\n    const getKey1 = getKeyFunction(key1)\n    const key2 = createKey()\n    const getKey2 = getKeyFunction(key2)\n\n    const response1 = createResponse('foo', { delay: 50 })\n    const response2 = createResponse('bar', { delay: 50 })\n\n    const fetcher1 = () => response1\n    const fetcher2 = () => response2\n    function Page() {\n      const { data: data1 } = useSWRInfinite(getKey1, fetcher1, {\n        suspense: true\n      })\n      const { data: data2 } = useSWRInfinite(getKey2, fetcher2, {\n        suspense: true\n      })\n      return (\n        <div>\n          data:{data1}:{data2}\n        </div>\n      )\n    }\n    preload(getKey1(0), fetcher1)\n    preload(getKey2(0), fetcher2)\n\n    renderWithConfig(\n      <Suspense fallback=\"loading\">\n        <Page />\n      </Suspense>\n    )\n    screen.getByText('loading')\n    //Should avoid waterfall(50ms + 50ms)\n    await act(() => sleep(80))\n    screen.getByText('data:foo:bar')\n  })\n\n  it('reset the preload result when the preload function gets an error', async () => {\n    const key = createKey()\n    const getKey = getKeyFunction(key)\n    let count = 0\n\n    const fetcher = () => {\n      ++count\n      const res = count === 1 ? new Error('err') : 'foo'\n      return createResponse(res)\n    }\n\n    let mutate\n    function Page() {\n      const { data, error, ...swr } = useSWRInfinite<any>(getKey, fetcher)\n      mutate = swr.mutate\n\n      if (error) {\n        return <div>error:{error.message}</div>\n      }\n      return <div>data:{data}</div>\n    }\n\n    try {\n      // error\n      await preload(getKey(0), fetcher)\n    } catch (e) {\n      // noop\n    }\n\n    renderWithConfig(<Page />)\n    screen.getByText('data:')\n\n    // use the preloaded result\n    await screen.findByText('error:err')\n    expect(count).toBe(1)\n\n    // revalidate\n    await act(() => mutate(getKey(0)))\n    // should not use the preload data\n    await screen.findByText('data:foo')\n  })\n\n  it('dedupe requests during preloading', async () => {\n    const key = createKey()\n    const getKey = getKeyFunction(key)\n\n    const fetcher = jest.fn(() =>\n      createResponse('foo', {\n        delay: 50\n      })\n    )\n    const onRender = jest.fn()\n\n    function Page() {\n      const { data } = useSWRInfinite(getKey, fetcher, { dedupingInterval: 0 })\n      return (\n        <Profiler id={key} onRender={onRender}>\n          data:{data}\n        </Profiler>\n      )\n    }\n\n    preload(getKey(0), fetcher)\n    expect(fetcher).toHaveBeenCalledTimes(1)\n\n    const { rerender } = renderWithConfig(<Page />)\n    expect(onRender).toHaveBeenCalledTimes(1)\n    // rerender when the preloading is in-flight, and the deduping interval is over\n    await act(() => sleep(10))\n    rerender(<Page />)\n    expect(onRender).toHaveBeenCalledTimes(2)\n\n    await screen.findByText('data:foo')\n    expect(fetcher).toHaveBeenCalledTimes(1)\n    expect(onRender).toHaveBeenCalledTimes(3)\n  })\n\n  it('should pass serialize key to fetcher', async () => {\n    const key = createKey()\n    const getKey = getKeyFunction(key)\n    let calledWith: string\n\n    const fetcher = (args: string) => {\n      calledWith = args\n    }\n\n    preload(() => getKey(0), fetcher)\n    expect(calledWith).toBe(getKey(0))\n  })\n  it('should not break parallel option', async () => {\n    // mock api\n    const pageData = ['apple', 'banana', 'pineapple']\n\n    const key = createKey()\n    const fetcher = ([_, index]) =>\n      createResponse(`${pageData[index]}, `, { delay: index === 0 ? 50 : 200 })\n    function Page() {\n      const { data } = useSWRInfinite(index => [key, index], fetcher, {\n        initialSize: 3,\n        parallel: true\n      })\n\n      return <div>data:{data}</div>\n    }\n    preload([key, 0], fetcher)\n    renderWithConfig(<Page />)\n    screen.getByText('data:')\n    // If SWR sends parallel requests, it should only take 200ms\n    await act(() => sleep(200))\n    screen.getByText('data:apple, banana, pineapple,')\n  })\n  it('should be able to preload multiple page', async () => {\n    // mock api\n    const pageData = ['apple', 'banana', 'pineapple']\n\n    const key = createKey()\n    const fetcher = ([_, index]) =>\n      createResponse(`${pageData[index]}, `, { delay: 50 })\n    function Page() {\n      const { data } = useSWRInfinite(index => [key, index], fetcher, {\n        initialSize: 3,\n        parallel: true\n      })\n\n      return <div>data:{data}</div>\n    }\n    preload([key, 0], fetcher)\n    preload([key, 1], fetcher)\n    preload([key, 2], fetcher)\n    renderWithConfig(<Page />)\n    screen.getByText('data:')\n    await act(() => sleep(50))\n    screen.getByText('data:apple, banana, pineapple,')\n  })\n})\n"
  },
  {
    "path": "test/use-swr-infinite.test.tsx",
    "content": "import { Suspense, useEffect, useState, act } from 'react'\nimport { fireEvent, screen } from '@testing-library/react'\nimport useSWR, { mutate as globalMutate, useSWRConfig, SWRConfig } from 'swr'\nimport useSWRInfinite, { unstable_serialize } from 'swr/infinite'\nimport {\n  sleep,\n  createKey,\n  createResponse,\n  nextTick,\n  renderWithConfig,\n  renderWithGlobalCache,\n  executeWithoutBatching,\n  itShouldSkipForReactCanary\n} from './utils'\n\ndescribe('useSWRInfinite', () => {\n  it('should render the first page component', async () => {\n    const key = createKey()\n    function Page() {\n      const { data, error, isValidating } = useSWRInfinite(\n        index => `page-${index}-${key}`,\n        infiniteKey => createResponse(infiniteKey)\n      )\n\n      return (\n        <div>\n          <div>data:{data}</div>\n          <div>isArray:{Array.isArray(data) ? 'true' : 'false'}</div>\n          <div>error:{error}</div>\n          <div>isValidating:{isValidating.toString()}</div>\n        </div>\n      )\n    }\n\n    renderWithConfig(<Page />)\n    screen.getByText('data:')\n\n    await screen.findByText(`data:page-0-${key}`)\n    await screen.findByText(`isArray:true`)\n    await screen.findByText(`error:`)\n    await screen.findByText(`isValidating:false`)\n  })\n\n  it('should not render anything if getKey throw error and call mutate wont cause error', async () => {\n    function Page() {\n      const { data, error, isValidating, mutate } = useSWRInfinite(\n        () => {\n          throw new Error('error')\n        },\n        infiniteKey => createResponse(infiniteKey)\n      )\n\n      return (\n        <div>\n          <div onClick={() => mutate()}>data:{data}</div>\n          <div>error:{error}</div>\n          <div>isValidating:{isValidating.toString()}</div>\n        </div>\n      )\n    }\n\n    renderWithConfig(<Page />)\n    screen.getByText('data:')\n\n    await screen.findByText(`data:`)\n    await screen.findByText(`error:`)\n    await screen.findByText(`isValidating:false`)\n\n    fireEvent.click(screen.getByText('data:'))\n\n    await screen.findByText(`data:`)\n    await screen.findByText(`error:`)\n    await screen.findByText(`isValidating:false`)\n  })\n\n  it('should render the multiple pages', async () => {\n    const key = createKey()\n    function Page() {\n      const { data, size, setSize } = useSWRInfinite(\n        index => [key, index],\n        ([_, index]) => createResponse(`page ${index}, `)\n      )\n\n      useEffect(() => {\n        // load next page if the current one is ready\n        if (size <= 2) setSize(size + 1)\n        // The setSize function is guaranteed to be referential equal\n        // eslint-disable-next-line react-hooks/exhaustive-deps\n      }, [size])\n\n      return <div>data:{data}</div>\n    }\n\n    renderWithConfig(<Page />)\n    screen.getByText('data:')\n\n    await screen.findByText('data:page 0, page 1, page 2,')\n  })\n\n  it('should support mutate and initialSize', async () => {\n    // mock api\n    const pageData = ['apple', 'banana', 'pineapple']\n\n    const key = createKey()\n    function Page() {\n      const { data, mutate: boundMutate } = useSWRInfinite(\n        index => [key, index],\n        ([_, index]) => createResponse(`${pageData[index]}, `),\n        {\n          initialSize: 3\n        }\n      )\n\n      return (\n        <div\n          onClick={() => {\n            // reload the entire list\n            boundMutate()\n          }}\n        >\n          data:{data}\n        </div>\n      )\n    }\n\n    renderWithConfig(<Page />)\n    screen.getByText('data:')\n\n    await screen.findByText('data:apple, banana, pineapple,')\n\n    // change the source data to 'watermelon'\n    pageData[1] = 'watermelon'\n\n    // revalidate\n    fireEvent.click(screen.getByText('data:apple, banana, pineapple,'))\n    await screen.findByText('data:apple, watermelon, pineapple,')\n  })\n\n  it('should support api cursor', async () => {\n    // an API that supports the `?offset=` param\n    async function mockAPIFetcher(url) {\n      const parse = url.match(/\\?offset=(\\d+)/)\n      const offset = parse ? +parse[1] + 1 : 0\n      const response =\n        offset <= 3\n          ? [\n              {\n                data: 'foo',\n                id: offset\n              },\n              {\n                data: 'bar',\n                id: offset + 1\n              }\n            ]\n          : []\n      return createResponse(response)\n    }\n\n    const key = createKey()\n    function Page() {\n      const { data } = useSWRInfinite(\n        (index, previousPageData) => {\n          // first page\n          if (index === 0) return `/api/${key}`\n\n          // hit the end\n          if (!previousPageData.length) {\n            return null\n          }\n\n          // fetch with offset\n          return `/api/${key}?offset=${\n            previousPageData[previousPageData.length - 1].id\n          }`\n        },\n        mockAPIFetcher,\n        {\n          initialSize: 5\n        }\n      )\n\n      if (!data) return <div>loading</div>\n\n      const hitEnd = data[data.length - 1].length === 0\n      return (\n        <div>\n          {data.map(page => {\n            return page.map(item => {\n              return (\n                <span key={item.id}>\n                  {item.id}: {item.data},{' '}\n                </span>\n              )\n            })\n          })}\n          {hitEnd ? 'end.' : ''}\n        </div>\n      )\n    }\n\n    renderWithConfig(<Page />)\n    screen.getByText('loading')\n\n    await screen.findByText('0: foo,')\n    await screen.findByText('1: bar,')\n    await screen.findByText('2: foo,')\n    await screen.findByText('3: bar,')\n    await screen.findByText('end.')\n  })\n\n  it('should skip fetching existing pages when loading more', async () => {\n    let requests = 0\n    const key = createKey()\n\n    function Page() {\n      const { data, size, setSize } = useSWRInfinite(\n        index => [key, index],\n        ([_, index]) => {\n          requests++\n          return createResponse(`page ${index}, `)\n        }\n      )\n\n      return (\n        <div\n          onClick={() => {\n            // load next page\n            setSize(size + 1)\n          }}\n        >\n          data:{data}\n        </div>\n      )\n    }\n\n    renderWithConfig(<Page />)\n    screen.getByText('data:')\n\n    await screen.findByText('data:page 0,') // mounted\n    expect(requests).toEqual(1)\n\n    // load next page\n    fireEvent.click(screen.getByText('data:page 0,'))\n\n    await screen.findByText('data:page 0, page 1,') // mounted\n    expect(requests).toEqual(3) // revalidate page 0, load page 1\n\n    // load next page\n    fireEvent.click(screen.getByText('data:page 0, page 1,'))\n\n    await screen.findByText('data:page 0, page 1, page 2,') // mounted\n    expect(requests).toEqual(5) // revalidate page 0, load page 2\n\n    // load next page\n    fireEvent.click(screen.getByText('data:page 0, page 1, page 2,'))\n\n    await screen.findByText('data:page 0, page 1, page 2, page 3,') // mounted\n    expect(requests).toEqual(7) // revalidate page 0, load page 3\n  })\n\n  it('should not revalidate page 0 when revalidateFirstPage is false', async () => {\n    let requests = 0\n    const key = createKey()\n\n    function Page() {\n      const { data, size, setSize } = useSWRInfinite(\n        index => [key, index],\n        ([_, index]) => {\n          requests++\n          return createResponse(`page ${index}, `)\n        },\n        {\n          revalidateFirstPage: false\n        }\n      )\n\n      return (\n        <div\n          onClick={() => {\n            // load next page\n            setSize(size + 1)\n          }}\n        >\n          data:{data}\n        </div>\n      )\n    }\n\n    renderWithConfig(<Page />)\n    screen.getByText('data:')\n\n    await screen.findByText('data:page 0,') // mounted\n    expect(requests).toEqual(1)\n\n    // load next page\n    fireEvent.click(screen.getByText('data:page 0,'))\n\n    await screen.findByText('data:page 0, page 1,') // mounted\n    expect(requests).toEqual(2) // load page 1\n\n    // load next page\n    fireEvent.click(screen.getByText('data:page 0, page 1,'))\n\n    await screen.findByText('data:page 0, page 1, page 2,') // mounted\n    expect(requests).toEqual(3) // load page 2\n\n    // load next page\n    fireEvent.click(screen.getByText('data:page 0, page 1, page 2,'))\n\n    await screen.findByText('data:page 0, page 1, page 2, page 3,') // mounted\n    expect(requests).toEqual(4) // load page 3\n  })\n\n  it('should cache page count', async () => {\n    let toggle\n\n    const key = createKey()\n    function Page() {\n      const { data, size, setSize } = useSWRInfinite(\n        index => [key, index],\n        ([_, index]) => createResponse(`page ${index}, `)\n      )\n\n      return (\n        <div\n          onClick={() => {\n            // load next page\n            setSize(size + 1)\n          }}\n        >\n          data:{data}\n        </div>\n      )\n    }\n\n    function App() {\n      const [showList, setShowList] = useState(true)\n      toggle = setShowList\n      return showList ? <Page /> : <div>yo</div>\n    }\n\n    renderWithConfig(<App />)\n    screen.getByText('data:')\n\n    await screen.findByText('data:page 0,')\n\n    // load next page\n    fireEvent.click(screen.getByText('data:page 0,'))\n    await screen.findByText('data:page 0, page 1,')\n\n    // switch to another component\n    act(() => toggle(v => !v))\n    screen.getByText('yo')\n\n    // switch back and it should still have 2 pages cached\n    act(() => toggle(v => !v))\n    screen.getByText('data:page 0, page 1,')\n    await act(() => sleep(100))\n    screen.getByText('data:page 0, page 1,')\n  })\n\n  it('should reset page size when key changes', async () => {\n    const key = createKey()\n    function Page() {\n      const [t, setT] = useState(false)\n      const {\n        data,\n        size: pageSize,\n        setSize\n      } = useSWRInfinite(\n        index => [key, index, t ? 'A' : 'B'],\n        ([_, index]) =>\n          createResponse(`page ${index}, `, {\n            delay: 20\n          }),\n        {\n          dedupingInterval: 0\n        }\n      )\n      return (\n        <>\n          <button onClick={() => setT(v => !v)}>change key</button>\n          <button onClick={() => setSize(size => size + 1)}>load next</button>\n          <div>size: {pageSize}</div>\n          <div>\n            key:{t ? 'A' : 'B'} data:{data}\n          </div>\n        </>\n      )\n    }\n\n    renderWithConfig(<Page />)\n    screen.getByText('key:B data:')\n\n    await screen.findByText('key:B data:page 0,')\n\n    // load next page\n    fireEvent.click(screen.getByText('load next'))\n    await screen.findByText('key:B data:page 0, page 1,')\n\n    // switch key, it should have only 1 page\n    fireEvent.click(screen.getByText('change key'))\n    await screen.findByText('key:A data:page 0,')\n\n    // switch key back, it should have 2 pages\n    fireEvent.click(screen.getByText('change key'))\n    await act(() => sleep(40))\n    await screen.findByText('key:B data:page 0, page 1,')\n  })\n\n  it('should persist page size when key changes', async () => {\n    let toggle\n\n    const key = createKey()\n    function Page() {\n      const [t, setT] = useState(false)\n      const { data, size, setSize } = useSWRInfinite(\n        index => [key, index, t ? 'A' : 'B'],\n        async ([_, index]) => createResponse(`page ${index}, `),\n        {\n          persistSize: true\n        }\n      )\n\n      toggle = setT\n\n      return (\n        <div\n          onClick={() => {\n            // load next page\n            setSize(size + 1)\n          }}\n        >\n          data:{data}\n        </div>\n      )\n    }\n\n    renderWithConfig(<Page />)\n    screen.getByText('data:')\n\n    await screen.findByText('data:page 0,')\n\n    // load next page\n    fireEvent.click(screen.getByText('data:page 0,'))\n\n    await screen.findByText('data:page 0, page 1,')\n\n    // switch key, it should still have 2 pages\n    act(() => toggle(v => !v))\n    await screen.findByText('data:page 0, page 1,')\n  })\n\n  it('should persist page size when remount', async () => {\n    let toggle\n\n    const key = createKey()\n    function Comp() {\n      const { data, size, setSize } = useSWRInfinite(\n        index => [key, index],\n        ([_, index]) => createResponse(`page ${index}, `)\n      )\n\n      return (\n        <div\n          onClick={() => {\n            // load next page\n            setSize(size + 1)\n          }}\n        >\n          data:{data}\n        </div>\n      )\n    }\n\n    function Page() {\n      const [show, setShow] = useState(true)\n      toggle = setShow\n      return show ? <Comp /> : <div>hide</div>\n    }\n\n    renderWithConfig(<Page />)\n    screen.getByText('data:')\n\n    await screen.findByText('data:page 0,')\n\n    // load next page\n    fireEvent.click(screen.getByText('data:page 0,'))\n    await screen.findByText('data:page 0, page 1,')\n\n    // pages should be unmounted now\n    act(() => toggle(v => !v))\n    await screen.findByText('hide')\n\n    // remount, it should still have 2 pages\n    act(() => toggle(v => !v))\n    await screen.findByText('data:page 0, page 1,')\n  })\n\n  it('should keep `mutate` referential equal', async () => {\n    const setters = []\n\n    const key = createKey()\n    function Comp() {\n      const { data, size, setSize } = useSWRInfinite(\n        index => [key, index],\n        ([_, index]) => createResponse(`page ${index}, `)\n      )\n\n      setters.push(setSize)\n\n      return (\n        <div\n          onClick={() => {\n            // load next page\n            setSize(size + 1)\n          }}\n        >\n          data:{data}\n        </div>\n      )\n    }\n\n    renderWithConfig(<Comp />)\n    screen.getByText('data:')\n\n    await screen.findByText('data:page 0,')\n\n    // load next page\n    fireEvent.click(screen.getByText('data:page 0,'))\n\n    await screen.findByText('data:page 0, page 1,')\n\n    // check all `setSize`s are referential equal.\n    for (const setSize of setters) {\n      expect(setSize).toEqual(setters[0])\n    }\n  })\n\n  it('should share initial cache from `useSWR`', async () => {\n    const cachedData = new Date().toISOString()\n    const key = createKey()\n    globalMutate(`shared-cache-${key}-0`, cachedData)\n\n    function Page() {\n      const { data } = useSWRInfinite<string, string>(\n        index => `shared-cache-${key}-${index}`,\n        () => createResponse(cachedData)\n      )\n\n      return <div>data:{data}</div>\n    }\n    renderWithGlobalCache(<Page />)\n    screen.getByText('data:')\n\n    // after a rerender, we should already have the cached data rendered\n    await screen.findByText(`data:${cachedData}`)\n  })\n\n  it('should not break refreshInterval', async () => {\n    let value = 0\n    const key = createKey()\n    function Page() {\n      const { data } = useSWRInfinite(\n        index => `interval-${key}-${index}`,\n        () => value++,\n        {\n          dedupingInterval: 0,\n          refreshInterval: 100\n        }\n      )\n\n      return <div>data:{data}</div>\n    }\n    renderWithConfig(<Page />)\n    screen.getByText('data:')\n\n    // after 300ms the rendered result should be 3\n    await executeWithoutBatching(() => sleep(350))\n    screen.getByText('data:3')\n  })\n\n  it('should re-use fallbackData', async () => {\n    const dummyResponses = {\n      '/api?page=1': ['page-1-1', 'page-1-2'],\n      '/api?page=2': ['page-2-1', 'page-2-2']\n    }\n    const requests = []\n\n    const key = createKey()\n    function Page() {\n      const { data, size, setSize } = useSWRInfinite(\n        index => {\n          return [key, `/api?page=${index + 1}`]\n        },\n        ([_, index]) => {\n          requests.push(index)\n          return createResponse<string[]>(dummyResponses[index])\n        },\n        {\n          fallbackData: [dummyResponses[`/api?page=1`]]\n        }\n      )\n\n      return (\n        <div\n          onClick={() => {\n            // load next page\n            setSize(size + 1)\n          }}\n        >\n          data:{(data ? [].concat(...data) : []).join(', ')}\n        </div>\n      )\n    }\n\n    renderWithConfig(<Page />)\n    // render with the fallbackData\n    screen.getByText('data:page-1-1, page-1-2')\n    expect(requests).toEqual([]) // should use the initial data\n\n    fireEvent.click(screen.getByText('data:page-1-1, page-1-2'))\n    // Should this reuse the cached data for `page=1`?\n    await screen.findByText('data:page-1-1, page-1-2, page-2-1, page-2-2')\n    expect(requests).toEqual(['/api?page=1', '/api?page=2'])\n  })\n\n  it('should share data between multiple hooks have the same key', async () => {\n    const key = createKey()\n    const dummyResponses = {\n      '/api?page=1': ['page-1-1', 'page-1-2'],\n      '/api?page=2': ['page-2-1', 'page-2-2']\n    }\n    const useCustomSWRInfinite = () => {\n      const { data, setSize, size } = useSWRInfinite(\n        index => [key, `/api?page=${index + 1}`],\n        ([_, index]) => createResponse<string[]>(dummyResponses[index])\n      )\n      return {\n        data: data ? [].concat(...data) : [],\n        setSize,\n        size\n      }\n    }\n\n    const Component = (props: { label: string }) => {\n      const { data, size, setSize } = useCustomSWRInfinite()\n      return (\n        <>\n          <ul>\n            {data.map(value => (\n              <li key={value}>\n                {props.label}:{value}\n              </li>\n            ))}\n          </ul>\n          <button onClick={() => setSize(size + 1)}>{props.label}:click</button>\n        </>\n      )\n    }\n\n    function Page() {\n      return (\n        <div>\n          <Component label=\"A\" />\n          <Component label=\"B\" />\n        </div>\n      )\n    }\n\n    renderWithConfig(<Page />)\n\n    // render responses for page=1\n    await screen.findByText('A:page-1-2')\n    await screen.findByText('B:page-1-2')\n\n    fireEvent.click(screen.getByText('A:click'))\n\n    // render responses for page=2\n    await screen.findByText('A:page-2-2')\n    await screen.findByText('B:page-2-2')\n  })\n\n  it('should support null as getKey', async () => {\n    function Page() {\n      const { data, setSize } = useSWRInfinite(null, () => 'data')\n\n      return (\n        <div\n          onClick={() => {\n            // load next page\n            setSize(size => size + 1)\n          }}\n        >\n          data:{data || ''}\n        </div>\n      )\n    }\n\n    renderWithConfig(<Page />)\n    screen.getByText('data:')\n    await screen.findByText('data:')\n\n    // load next page\n    fireEvent.click(screen.getByText('data:'))\n    await screen.findByText('data:')\n  })\n\n  it('should support getKey to return null', async () => {\n    function Page() {\n      const { data, setSize } = useSWRInfinite(\n        () => null,\n        () => 'data'\n      )\n\n      return (\n        <div\n          onClick={() => {\n            // load next page\n            setSize(size => size + 1)\n          }}\n        >\n          data:{data || ''}\n        </div>\n      )\n    }\n\n    renderWithConfig(<Page />)\n    screen.getByText('data:')\n    await screen.findByText('data:')\n\n    // load next page\n    fireEvent.click(screen.getByText('data:'))\n    await screen.findByText('data:')\n  })\n\n  it('should mutate a cache with `unstable_serialize`', async () => {\n    let count = 0\n    const key = createKey()\n    let mutate\n    function Page() {\n      mutate = useSWRConfig().mutate\n      const { data } = useSWRInfinite(\n        index => `page-test-${key}-${index}`,\n        infiniteKey => createResponse(`${infiniteKey}:${++count}`)\n      )\n      return <div>data:{data}</div>\n    }\n\n    renderWithConfig(<Page />)\n    screen.getByText('data:')\n\n    await screen.findByText(`data:page-test-${key}-0:1`)\n\n    await act(() =>\n      mutate(unstable_serialize(index => `page-test-${key}-${index}`))\n    )\n    await screen.findByText(`data:page-test-${key}-0:2`)\n\n    await act(() =>\n      mutate(\n        unstable_serialize(index => `page-test-${key}-${index}`),\n        'local-mutation',\n        false\n      )\n    )\n    await screen.findByText('data:local-mutation')\n  })\n\n  it('should mutate a cache with `unstable_serialize` based on a current data', async () => {\n    const key = createKey()\n    const getKey: (index: number) => [string, number] = (index: number) => [\n      key,\n      index\n    ]\n    let mutate\n    function Comp() {\n      mutate = useSWRConfig().mutate\n      const { data, size, setSize } = useSWRInfinite(getKey, ([_, index]) =>\n        createResponse(`page ${index}, `)\n      )\n      useEffect(() => {\n        setSize(size + 1)\n        // eslint-disable-next-line react-hooks/exhaustive-deps\n      }, [])\n      return <div>data:{data}</div>\n    }\n\n    renderWithConfig(<Comp />)\n\n    screen.getByText('data:')\n    await screen.findByText('data:page 0, page 1,')\n\n    await act(() =>\n      mutate(\n        unstable_serialize(getKey),\n        data => data.map(value => `(edited)${value}`),\n        false\n      )\n    )\n    await screen.findByText('data:(edited)page 0, (edited)page 1,')\n  })\n\n  it('should be able to use `unstable_serialize` with a custom cache', async () => {\n    const key = createKey()\n\n    let mutateCustomCache\n\n    function Page() {\n      mutateCustomCache = useSWRConfig().mutate\n      const { data } = useSWRInfinite(\n        () => key,\n        () => createResponse('response data')\n      )\n      return <div>data:{data}</div>\n    }\n    function App() {\n      return (\n        <SWRConfig\n          value={{\n            provider: () => new Map([[key, { data: 'initial-cache' }]])\n          }}\n        >\n          <Page />\n        </SWRConfig>\n      )\n    }\n\n    renderWithConfig(<App />)\n    screen.getByText('data:')\n\n    await screen.findByText('data:initial-cache')\n\n    await act(() => mutateCustomCache(unstable_serialize(() => key)))\n    await screen.findByText('data:response data')\n\n    await act(() =>\n      mutateCustomCache(\n        unstable_serialize(() => key),\n        'local-mutation',\n        false\n      )\n    )\n    await screen.findByText('data:local-mutation')\n  })\n\n  it('should correctly set size when key is null', async () => {\n    const loggedValues = []\n\n    function Page() {\n      const { size, setSize } = useSWRInfinite(\n        () => null,\n        () => ''\n      )\n      loggedValues.push(size)\n      return <button onClick={() => setSize(1)}>set size</button>\n    }\n\n    renderWithConfig(<Page />)\n\n    await screen.findByText('set size')\n    fireEvent.click(screen.getByText('set size'))\n    await nextTick()\n\n    expect(loggedValues).toEqual([1])\n  })\n\n  it('setSize should only accept number', async () => {\n    const key = createKey()\n    function Comp() {\n      const { data, size, setSize } = useSWRInfinite(\n        index => [key, index],\n        ([_, index]) => createResponse(`page ${index}`)\n      )\n\n      return (\n        <>\n          <div\n            onClick={() => {\n              // load next page\n              // @ts-expect-error\n              setSize('2')\n            }}\n          >\n            data:{data}\n          </div>\n          <div>size:{size}</div>\n        </>\n      )\n    }\n    renderWithConfig(<Comp></Comp>)\n    await screen.findByText('data:page 0')\n    await screen.findByText('size:1')\n\n    fireEvent.click(screen.getByText('data:page 0'))\n\n    await screen.findByText('data:page 0')\n    await screen.findByText('size:1')\n  })\n\n  it('should correctly set size when setSize receives a callback', async () => {\n    const key = createKey()\n\n    function Page() {\n      const { data, size, setSize } = useSWRInfinite(\n        index => `${key}-${index}`,\n        k => createResponse(`page-${k}`)\n      )\n      return (\n        <div>\n          <p>data: {(data || []).join()}</p>\n          <p>size: {size}</p>\n          <button onClick={() => setSize(sz => sz + 1)}>set size</button>\n        </div>\n      )\n    }\n\n    const getDataBySize = size =>\n      Array<string>(size)\n        .fill('')\n        .map((_, index) => `page-${key}-${index}`)\n        .join()\n\n    renderWithConfig(<Page />)\n\n    await screen.findByText('set size')\n    const btn = screen.getByText('set size')\n\n    fireEvent.click(btn)\n    await nextTick()\n    await screen.findByText(`data: ${getDataBySize(2)}`)\n    await screen.findByText('size: 2')\n\n    fireEvent.click(btn)\n    await nextTick()\n    await screen.findByText(`data: ${getDataBySize(3)}`)\n    await screen.findByText('size: 3')\n  })\n\n  it('setSize should return a promise', async () => {\n    let _setSize\n    function Comp() {\n      const { setSize } = useSWRInfinite(\n        () => null,\n        () => createResponse('')\n      )\n\n      _setSize = setSize\n      return null\n    }\n    renderWithConfig(<Comp />)\n    expect(_setSize()).toBeInstanceOf(Promise)\n  })\n\n  // https://github.com/vercel/swr/issues/908\n  it('should revalidate first page after mutating', async () => {\n    const key = createKey()\n    let v = 'old'\n\n    function Page() {\n      const { data, mutate: boundMutate } = useSWRInfinite(\n        i => [key, i],\n        () => createResponse(v)\n      )\n\n      return (\n        <div>\n          <button\n            onClick={() => {\n              v = 'new'\n              boundMutate([v])\n            }}\n          >\n            mutate\n          </button>\n          <p>data:{data}</p>\n        </div>\n      )\n    }\n\n    renderWithConfig(<Page />)\n\n    await screen.findByText('data:old')\n    fireEvent.click(screen.getByText('mutate'))\n    await screen.findByText('data:new')\n  })\n\n  it('should reuse cached value for new pages', async () => {\n    const key = createKey()\n\n    function Page() {\n      const { data, setSize } = useSWRInfinite(\n        index => key + '-' + index,\n        () => createResponse('response value')\n      )\n      return (\n        <div onClick={() => setSize(2)}>data:{data ? data.join(',') : ''}</div>\n      )\n    }\n\n    renderWithConfig(<Page />, {\n      provider: () => new Map([[key + '-1', { data: 'cached value' }]])\n    })\n\n    screen.getByText('data:')\n    await screen.findByText('data:response value')\n    fireEvent.click(screen.getByText('data:response value'))\n    await screen.findByText('data:response value,cached value')\n  })\n\n  it('should return cached value ASAP when updating size and revalidate in the background', async () => {\n    const key = createKey()\n    const getData = jest.fn(v => v)\n\n    function Page() {\n      const { data, setSize } = useSWRInfinite(\n        index => key + '-' + index,\n        () => sleep(30).then(() => getData('response value'))\n      )\n      return (\n        <div onClick={() => setSize(2)}>data:{data ? data.join(',') : ''}</div>\n      )\n    }\n    renderWithConfig(<Page />, {\n      provider: () => new Map([[key + '-1', { data: 'cached value' }]])\n    })\n\n    screen.getByText('data:')\n    await screen.findByText('data:response value')\n    expect(getData).toHaveBeenCalledTimes(1)\n\n    fireEvent.click(screen.getByText('data:response value'))\n\n    // Returned directly from the cache without blocking\n    await screen.findByText('data:response value,cached value')\n    expect(getData).toHaveBeenCalledTimes(1)\n\n    // Revalidate\n    await act(() => sleep(30))\n    expect(getData).toHaveBeenCalledTimes(2)\n  })\n\n  it('should block on fetching new uncached pages when updating size', async () => {\n    const key = createKey()\n    const getData = jest.fn(v => v)\n\n    function Page() {\n      const { data, setSize } = useSWRInfinite(\n        index => key + '-' + index,\n        () => sleep(30).then(() => getData('response value'))\n      )\n      return (\n        <div onClick={() => setSize(2)}>data:{data ? data.join(',') : ''}</div>\n      )\n    }\n\n    renderWithConfig(<Page />)\n\n    screen.getByText('data:')\n    await screen.findByText('data:response value')\n    expect(getData).toHaveBeenCalledTimes(1)\n\n    fireEvent.click(screen.getByText('data:response value'))\n\n    // Fetch a new page and revalidate the first page.\n    await screen.findByText('data:response value,response value')\n    expect(getData).toHaveBeenCalledTimes(3)\n  })\n\n  it('should return fallbackData if cache is empty', async () => {\n    const key = createKey()\n\n    function Page() {\n      const { data, setSize } = useSWRInfinite(\n        index => key + '-' + index,\n        () => sleep(30).then(() => 'response value'),\n        { fallbackData: ['fallback-1', 'fallback-2'] }\n      )\n      return (\n        <div onClick={() => setSize(2)}>data:{data ? data.join(',') : ''}</div>\n      )\n    }\n    renderWithConfig(<Page />)\n\n    screen.getByText('data:fallback-1,fallback-2')\n\n    // Update size, it should still render the fallback\n    fireEvent.click(screen.getByText('data:fallback-1,fallback-2'))\n    await nextTick()\n    screen.getByText('data:fallback-1,fallback-2')\n  })\n\n  it('should revalidate the resource with bound mutate when no argument is passed', async () => {\n    let t = 0\n    const key = createKey()\n    const fetcher = jest.fn(async () =>\n      createResponse(`foo-${t++}`, { delay: 10 })\n    )\n    const logger = []\n    function Page() {\n      const { data, mutate } = useSWRInfinite(() => key, fetcher, {\n        dedupingInterval: 0\n      })\n      logger.push(data)\n      return (\n        <>\n          <div>data: {String(data)}</div>\n          <button onClick={() => mutate()}>mutate</button>\n        </>\n      )\n    }\n\n    renderWithConfig(<Page />)\n    await screen.findByText('data: foo-0')\n\n    fireEvent.click(screen.getByText('mutate'))\n    await screen.findByText('data: foo-1')\n    expect(fetcher).toHaveBeenCalledTimes(2)\n\n    expect(logger).toEqual([undefined, ['foo-0'], ['foo-1']])\n  })\n\n  it('should pass the correct cursor information in `getKey`', async () => {\n    const key = createKey()\n    const fetcher = jest.fn(index => createResponse('data-' + index))\n    const logger = []\n    function Page() {\n      const { data } = useSWRInfinite(\n        (index, previousPageData) => {\n          logger.push(key + ':' + index + ':' + previousPageData)\n          return '' + index\n        },\n        fetcher,\n        {\n          dedupingInterval: 0,\n          initialSize: 5\n        }\n      )\n      return (\n        <>\n          <div>{data ? data.length : 0}</div>\n        </>\n      )\n    }\n\n    renderWithConfig(<Page />)\n    await screen.findByText('5')\n\n    expect(\n      logger.every(log => {\n        const [k, index, previousData] = log.split(':')\n        return (\n          k === key &&\n          ((index === '0' && previousData === 'null') ||\n            previousData === 'data-' + (index - 1))\n        )\n      })\n    ).toBeTruthy()\n  })\n\n  // https://github.com/vercel/swr/issues/1776\n  itShouldSkipForReactCanary(\n    'should update the getKey reference with the suspense mode',\n    async () => {\n      const keyA = 'keyA' + createKey()\n      const keyB = 'keyB' + createKey()\n\n      const apiData = {\n        [keyA]: ['A1', 'A2', 'A3'],\n        [keyB]: ['B1', 'B2', 'B3']\n      }\n\n      function Page() {\n        const [status, setStatus] = useState('a')\n        const { data, setSize } = useSWRInfinite(\n          () => (status === 'a' ? keyA : keyB),\n          key => createResponse(apiData[key]),\n          { suspense: true }\n        )\n        return (\n          <>\n            <div>data: {String(data)}</div>\n            <button\n              onClick={() => {\n                setStatus('b')\n                setSize(1)\n              }}\n            >\n              mutate\n            </button>\n          </>\n        )\n      }\n      renderWithConfig(\n        <Suspense fallback=\"loading\">\n          <Page />\n        </Suspense>\n      )\n      await screen.findByText('data: A1,A2,A3')\n\n      fireEvent.click(screen.getByText('mutate'))\n      await screen.findByText('data: B1,B2,B3')\n    }\n  )\n\n  it('should revalidate the resource with bound mutate when arguments are passed', async () => {\n    const key = createKey()\n\n    let counter = 0\n\n    function Content() {\n      const { data } = useSWRInfinite(\n        () => key,\n        () => createResponse(++counter),\n        {\n          revalidateOnMount: true,\n          revalidateFirstPage: false,\n          dedupingInterval: 0\n        }\n      )\n      return <div>data: {String(data)}</div>\n    }\n\n    function Page() {\n      const [contentKey, setContentKey] = useState('a')\n      return (\n        <>\n          <Content key={contentKey} />\n          <button\n            onClick={() => {\n              setContentKey('b')\n            }}\n          >\n            mutate\n          </button>\n        </>\n      )\n    }\n    renderWithConfig(<Page />)\n    await screen.findByText('data: 1')\n\n    fireEvent.click(screen.getByText('mutate'))\n    await screen.findByText('data: 2')\n  })\n\n  // https://github.com/vercel/swr/issues/1899\n  it('should revalidate the resource with bound mutate when options is of Object type ', async () => {\n    let t = 0\n    const key = createKey()\n    const fetcher = jest.fn(async () =>\n      createResponse(`foo-${t++}`, { delay: 10 })\n    )\n    const logger = []\n    function Page() {\n      const { data, mutate } = useSWRInfinite(() => key, fetcher, {\n        dedupingInterval: 0\n      })\n      logger.push(data)\n      return (\n        <>\n          <div>data: {String(data)}</div>\n          <button onClick={() => mutate(data, { revalidate: true })}>\n            mutate\n          </button>\n        </>\n      )\n    }\n\n    renderWithConfig(<Page />)\n    await screen.findByText('data: foo-0')\n\n    fireEvent.click(screen.getByText('mutate'))\n    await screen.findByText('data: foo-1')\n    expect(fetcher).toHaveBeenCalledTimes(2)\n\n    expect(logger).toEqual([undefined, ['foo-0'], ['foo-1']])\n  })\n\n  // https://github.com/vercel/swr/issues/1899\n  it('should not revalidate the resource with bound mutate when options is of Object type', async () => {\n    let t = 0\n    const key = createKey()\n    const fetcher = jest.fn(async () =>\n      createResponse(`foo-${t++}`, { delay: 10 })\n    )\n    const logger = []\n    function Page() {\n      const { data, mutate } = useSWRInfinite(() => key, fetcher, {\n        dedupingInterval: 0\n      })\n      logger.push(data)\n      return (\n        <>\n          <div>data: {String(data)}</div>\n          <button onClick={() => mutate(data, { revalidate: false })}>\n            mutate\n          </button>\n        </>\n      )\n    }\n\n    renderWithConfig(<Page />)\n    await screen.findByText('data: foo-0')\n\n    fireEvent.click(screen.getByText('mutate'))\n    expect(fetcher).toHaveBeenCalledTimes(1)\n\n    expect(logger).toEqual([undefined, ['foo-0']])\n  })\n\n  it('should be able to use the optimisticData option', async () => {\n    let t = 0\n    const key = createKey()\n    const fetcher = async () => createResponse(`foo-${t++}`)\n    const updater = async () => createResponse(['updated'])\n\n    function Page() {\n      const { data, mutate } = useSWRInfinite(() => key, fetcher, {\n        dedupingInterval: 0\n      })\n\n      return (\n        <>\n          <div>data: {String(data)}</div>\n          <button\n            onClick={() => {\n              mutate(updater, { optimisticData: ['optimistic'] })\n            }}\n          >\n            mutate\n          </button>\n        </>\n      )\n    }\n\n    renderWithConfig(<Page />)\n    await screen.findByText('data: foo-0')\n\n    fireEvent.click(screen.getByText('mutate'))\n\n    await screen.findByText('data: optimistic')\n    await screen.findByText('data: updated')\n    await screen.findByText('data: foo-1')\n  })\n\n  it('should be able to use the functional optimisticData option', async () => {\n    let t = 0\n    const key = createKey()\n    const fetcher = async () => createResponse(`foo-${t++}`)\n    const updater = async () => createResponse(['updated'])\n\n    function Page() {\n      const { data, mutate } = useSWRInfinite(() => key, fetcher, {\n        dedupingInterval: 0\n      })\n\n      return (\n        <>\n          <div>data: {String(data)}</div>\n          <button\n            onClick={() => {\n              mutate(updater, {\n                optimisticData: current => [...current, 'optimistic']\n              })\n            }}\n          >\n            mutate\n          </button>\n        </>\n      )\n    }\n\n    renderWithConfig(<Page />)\n    await screen.findByText('data: foo-0')\n\n    fireEvent.click(screen.getByText('mutate'))\n\n    await screen.findByText('data: foo-0,optimistic')\n    await screen.findByText('data: updated')\n    await screen.findByText('data: foo-1')\n  })\n\n  it('should be able to use the functional populateCache option', async () => {\n    let t = 0\n    const key = createKey()\n    const fetcher = async () => createResponse(`foo-${t++}`)\n    const updater = async () => createResponse(['updated'])\n\n    function Page() {\n      const { data, mutate } = useSWRInfinite(() => key, fetcher, {\n        dedupingInterval: 0\n      })\n\n      return (\n        <>\n          <div>data: {String(data)}</div>\n          <button\n            onClick={() => {\n              mutate(updater, {\n                populateCache: (result: string[], currentData: string[]) => {\n                  return [...currentData, ...result]\n                },\n                revalidate: false\n              })\n            }}\n          >\n            mutate\n          </button>\n        </>\n      )\n    }\n\n    renderWithConfig(<Page />)\n    await screen.findByText('data: foo-0')\n\n    fireEvent.click(screen.getByText('mutate'))\n\n    await screen.findByText('data: foo-0,updated')\n  })\n\n  it('should be able to turn off the populateCache option', async () => {\n    let t = 0\n    const key = createKey()\n    const fetcher = async () => createResponse(`foo-${t++}`)\n    const updater = async () => createResponse(['updated'])\n\n    function Page() {\n      const { data, mutate } = useSWRInfinite(() => key, fetcher, {\n        dedupingInterval: 0\n      })\n\n      return (\n        <>\n          <div>data: {String(data)}</div>\n          <button\n            onClick={() => {\n              mutate(updater, {\n                populateCache: false,\n                revalidate: false\n              })\n            }}\n          >\n            mutate\n          </button>\n        </>\n      )\n    }\n\n    renderWithConfig(<Page />)\n    await screen.findByText('data: foo-0')\n    fireEvent.click(screen.getByText('mutate'))\n    sleep(50)\n    await screen.findByText('data: foo-0')\n  })\n\n  it('should be able to use the functional optimisticData option with multiple pages', async () => {\n    const apiData = {\n      0: ['A1', 'A2', 'A3'],\n      1: ['B1', 'B2', 'B3']\n    }\n    const key = createKey()\n    const fetcher = async ([_key, pageKey]) => createResponse(apiData[pageKey])\n    const updater = async () => createResponse(['updated'])\n\n    function Page() {\n      const { data, mutate } = useSWRInfinite(\n        (pageIndex: number) => [key, pageIndex],\n        fetcher,\n        {\n          initialSize: 2,\n          dedupingInterval: 0\n        }\n      )\n\n      return (\n        <>\n          <div>data: {String(data)}</div>\n          <button\n            onClick={() => {\n              mutate(updater, {\n                optimisticData: current => [current[0], [...current[1], 'B4']],\n                populateCache: (result: string[], currentData: string[]) => {\n                  return [currentData[0], [...currentData[1], ...result]]\n                },\n                revalidate: false\n              })\n            }}\n          >\n            mutate\n          </button>\n        </>\n      )\n    }\n\n    renderWithConfig(<Page />)\n    await screen.findByText('data: A1,A2,A3,B1,B2,B3')\n\n    fireEvent.click(screen.getByText('mutate'))\n\n    await screen.findByText('data: A1,A2,A3,B1,B2,B3,B4')\n    await screen.findByText('data: A1,A2,A3,B1,B2,B3,updated')\n  })\n\n  it('should be able to use the rollbackOnError option', async () => {\n    let t = 0\n    const key = createKey()\n    const fetcher = async () => createResponse(`foo-${t++}`)\n    const updater = async () =>\n      createResponse(new Error('error')) as any as Promise<string[]>\n\n    function Page() {\n      const { data, mutate } = useSWRInfinite(() => key, fetcher, {\n        dedupingInterval: 0\n      })\n\n      return (\n        <>\n          <div>data: {String(data)}</div>\n          <button\n            onClick={async () => {\n              try {\n                await mutate(updater, {\n                  optimisticData: ['optimistic'],\n                  rollbackOnError: true,\n                  revalidate: false\n                })\n              } catch (e) {\n                // noop\n              }\n            }}\n          >\n            mutate\n          </button>\n        </>\n      )\n    }\n\n    renderWithConfig(<Page />)\n    await screen.findByText('data: foo-0')\n\n    fireEvent.click(screen.getByText('mutate'))\n\n    await screen.findByText('data: optimistic')\n    await screen.findByText('data: foo-0')\n  })\n\n  it('should be able to disable the rollbackOnError option', async () => {\n    let t = 0\n    const key = createKey()\n    const fetcher = async () => createResponse(`foo-${t++}`)\n    const updater = async () =>\n      createResponse(new Error('error')) as any as Promise<string[]>\n\n    function Page() {\n      const { data, mutate } = useSWRInfinite(() => key, fetcher, {\n        dedupingInterval: 0\n      })\n\n      return (\n        <>\n          <div>data: {String(data)}</div>\n          <button\n            onClick={async () => {\n              try {\n                await mutate(updater, {\n                  optimisticData: ['optimistic'],\n                  rollbackOnError: false,\n                  revalidate: false\n                })\n              } catch (e) {\n                // noop\n              }\n            }}\n          >\n            mutate\n          </button>\n        </>\n      )\n    }\n\n    renderWithConfig(<Page />)\n    await screen.findByText('data: foo-0')\n\n    fireEvent.click(screen.getByText('mutate'))\n\n    await screen.findByText('data: optimistic')\n    await sleep(50)\n    await screen.findByText('data: optimistic')\n  })\n\n  it('should be able to use the throwOnError option', async () => {\n    let t = 0\n    const key = createKey()\n    const fetcher = async () => createResponse(`foo-${t++}`)\n    const updater = async () =>\n      createResponse(new Error('mutation error')) as any as Promise<string[]>\n\n    function Page() {\n      const [error, setError] = useState(null)\n      const { data, mutate } = useSWRInfinite(() => key, fetcher, {\n        dedupingInterval: 0\n      })\n\n      return (\n        <>\n          <div>data: {String(data)}</div>\n          <div>error: {error}</div>\n          <button\n            onClick={async () => {\n              try {\n                await mutate(updater, { throwOnError: true, revalidate: false })\n              } catch (e) {\n                setError(e.message)\n              }\n            }}\n          >\n            mutate\n          </button>\n        </>\n      )\n    }\n\n    renderWithConfig(<Page />)\n    await screen.findByText('data: foo-0')\n\n    fireEvent.click(screen.getByText('mutate'))\n    await screen.findByText('error: mutation error')\n  })\n\n  it('should be able to disable the throwOnError option', async () => {\n    let t = 0\n    const key = createKey()\n    const fetcher = async () => createResponse(`foo-${t++}`)\n    const updater = async () =>\n      createResponse(new Error('mutation error')) as any as Promise<string[]>\n\n    function Page() {\n      const [error, setError] = useState(null)\n      const { data, mutate } = useSWRInfinite(() => key, fetcher, {\n        dedupingInterval: 0\n      })\n\n      return (\n        <>\n          <div>data: {String(data)}</div>\n          <div>error: {error}</div>\n          <button\n            onClick={async () => {\n              try {\n                await mutate(updater, {\n                  throwOnError: false,\n                  revalidate: false\n                })\n              } catch (e) {\n                setError(e.message)\n              }\n            }}\n          >\n            mutate\n          </button>\n        </>\n      )\n    }\n\n    renderWithConfig(<Page />)\n    await screen.findByText('data: foo-0')\n\n    fireEvent.click(screen.getByText('mutate'))\n    await sleep(50)\n    await screen.findByText('error:')\n  })\n\n  it('should share data with useSWR', async () => {\n    const key = createKey()\n    const SWR = () => {\n      const { data } = useSWR(`${key}-${2}`)\n      return <div>swr: {data}</div>\n    }\n    const Page = () => {\n      const { data, setSize, size } = useSWRInfinite(\n        index => `${key}-${index + 1}`,\n        infiniteKey => createResponse(`${infiniteKey},`, { delay: 100 })\n      )\n      return (\n        <>\n          <div onClick={() => setSize(i => i + 1)}>data: {data}</div>\n          <div onClick={() => setSize(i => i + 1)}>size: {size}</div>\n          <SWR></SWR>\n        </>\n      )\n    }\n    renderWithConfig(<Page />)\n    await screen.findByText(`data: ${key}-1,`)\n    await screen.findByText(`swr:`)\n    fireEvent.click(screen.getByText('size: 1'))\n    await screen.findByText(`data: ${key}-1,${key}-2,`)\n    await screen.findByText(`size: 2`)\n    await screen.findByText(`swr: ${key}-2,`)\n  })\n\n  it('should support the parallel option', async () => {\n    // mock api\n    const pageData = ['apple', 'banana', 'pineapple']\n\n    const key = createKey()\n    function Page() {\n      const { data } = useSWRInfinite(\n        index => [key, index],\n        ([_, index]) => createResponse(`${pageData[index]}, `, { delay: 50 }),\n        {\n          initialSize: 3,\n          parallel: true\n        }\n      )\n\n      return <div>data:{data}</div>\n    }\n\n    renderWithConfig(<Page />)\n    screen.getByText('data:')\n\n    // If SWR sends requests sequentially, it takes 150ms at least\n    await act(() => sleep(100))\n    screen.getByText('data:apple, banana, pineapple,')\n  })\n\n  it('should return the first error happened in parallel requests', async () => {\n    // mock api\n    const pageData = [\n      { data: new Error('apple'), delay: 50 },\n      { data: new Error('banana'), delay: 30 },\n      { data: 'pineapple', delay: 10 }\n    ]\n\n    const key = createKey()\n    function Page() {\n      const { data, error } = useSWRInfinite(\n        index => [key, index],\n        ([_, index]) =>\n          createResponse<string>(pageData[index].data as string, {\n            delay: pageData[index].delay\n          }),\n        {\n          initialSize: 3,\n          parallel: true\n        }\n      )\n\n      if (error) {\n        return <div>error:{error.message}</div>\n      }\n\n      return <div>data:{data}</div>\n    }\n\n    renderWithConfig(<Page />)\n    screen.getByText('data:')\n\n    await act(() => sleep(50))\n    screen.getByText('error:banana')\n  })\n\n  it('should send request sequentially when the parallel option is disabled', async () => {\n    // mock api\n    const pageData = ['apple', 'banana', 'pineapple']\n\n    const key = createKey()\n    function Page() {\n      const { data } = useSWRInfinite(\n        index => [key, index],\n        ([_, index]) => createResponse(`${pageData[index]}, `, { delay: 50 }),\n        {\n          initialSize: 3,\n          parallel: false\n        }\n      )\n\n      return <div>data:{data}</div>\n    }\n\n    renderWithConfig(<Page />)\n    screen.getByText('data:')\n\n    // If SWR sends requests sequentially, it takes 150ms at least\n    await act(() => sleep(100))\n    screen.getByText('data:')\n    await act(() => sleep(200))\n    screen.getByText('data:apple, banana, pineapple,')\n  })\n\n  it('should be the parallel option false by default', async () => {\n    // mock api\n    const pageData = ['apple', 'banana', 'pineapple']\n\n    const key = createKey()\n    function Page() {\n      const { data } = useSWRInfinite(\n        index => [key, index],\n        ([_, index]) => createResponse(`${pageData[index]}, `, { delay: 50 }),\n        {\n          initialSize: 3\n        }\n      )\n\n      return <div>data:{data}</div>\n    }\n\n    renderWithConfig(<Page />)\n    screen.getByText('data:')\n\n    // If SWR sends requests sequentially, it takes 150ms at least\n    await act(() => sleep(100))\n    screen.getByText('data:')\n    await act(() => sleep(200))\n    screen.getByText('data:apple, banana, pineapple,')\n  })\n\n  it('should make previousPageData null when the parallel option is enabled', async () => {\n    // mock api\n    const pageData = ['apple', 'banana', 'pineapple']\n\n    const previousPageDataLogs = []\n\n    const key = createKey()\n    function Page() {\n      const { data } = useSWRInfinite(\n        (index, previousPageData) => {\n          previousPageDataLogs.push(previousPageData)\n          return [key, index]\n        },\n        ([_, index]) => createResponse(`${pageData[index]}, `, { delay: 50 }),\n        {\n          initialSize: 3,\n          parallel: true\n        }\n      )\n\n      return <div>data:{data}</div>\n    }\n\n    renderWithConfig(<Page />)\n    screen.getByText('data:')\n\n    // If SWR sends requests sequentially, it takes 150ms at least\n    await act(() => sleep(100))\n    screen.getByText('data:apple, banana, pineapple,')\n    expect(previousPageDataLogs.every(d => d === null)).toBeTruthy()\n  })\n\n  it('should support revalidate as a function', async () => {\n    // mock api\n    let pageData = ['apple', 'banana', 'pineapple']\n\n    const key = createKey()\n    function Page() {\n      const { data, mutate: boundMutate } = useSWRInfinite(\n        index => [key, index],\n        ([_, index]) => createResponse(pageData[index]),\n        {\n          initialSize: 3\n        }\n      )\n\n      return (\n        <div\n          onClick={() => {\n            boundMutate(data, {\n              // only revalidate 'apple' & 'pineapple' (page=2)\n              revalidate: (d, [_, i]: [string, number]) => {\n                return d === 'apple' || i === 2\n              }\n            })\n          }}\n        >\n          data:{Array.isArray(data) && data.join(',')}\n        </div>\n      )\n    }\n\n    renderWithConfig(<Page />)\n    screen.getByText('data:')\n\n    await screen.findByText('data:apple,banana,pineapple')\n\n    // update response data\n    pageData = pageData.map(data => `[${data}]`)\n\n    // revalidate\n    fireEvent.click(screen.getByText('data:apple,banana,pineapple'))\n\n    await screen.findByText('data:[apple],banana,[pineapple]')\n  })\n})\n"
  },
  {
    "path": "test/use-swr-integration.test.tsx",
    "content": "import { screen, fireEvent } from '@testing-library/react'\nimport React, { useState, useEffect, Profiler, act } from 'react'\nimport useSWR from 'swr'\nimport {\n  createResponse,\n  sleep,\n  nextTick as waitForNextTick,\n  renderWithConfig,\n  createKey,\n  renderWithGlobalCache\n} from './utils'\n\ndescribe('useSWR', () => {\n  const sharedKey = createKey()\n  it('should return `undefined` on hydration then return data', async () => {\n    function Page() {\n      const { data } = useSWR(sharedKey, () => 'SWR')\n      return <div>hello, {data}</div>\n    }\n\n    renderWithGlobalCache(<Page />)\n    // hydration\n    screen.getByText('hello,')\n\n    // mounted\n    await screen.findByText('hello, SWR')\n  })\n\n  it('should allow functions as key and reuse the cache', async () => {\n    function Page() {\n      const { data } = useSWR(\n        () => sharedKey,\n        () => 'SWR'\n      )\n      return <div>hello, {data}</div>\n    }\n\n    renderWithGlobalCache(<Page />)\n    screen.getByText('hello, SWR')\n  })\n\n  it('should allow async fetcher functions', async () => {\n    const fetcher = jest.fn(() => createResponse('SWR'))\n    const key = createKey()\n    function Page() {\n      const { data } = useSWR(key, fetcher)\n      return <div>hello, {data}</div>\n    }\n\n    renderWithConfig(<Page />)\n    // hydration\n    screen.getByText('hello,')\n\n    await screen.findByText('hello, SWR')\n    expect(fetcher).toHaveBeenCalledTimes(1)\n  })\n\n  it('should not call fetch function when revalidateOnMount is false', async () => {\n    const fetch = jest.fn(() => 'SWR')\n\n    const key = createKey()\n    function Page() {\n      const { data } = useSWR(key, fetch, {\n        revalidateOnMount: false\n      })\n      return <div>hello, {data}</div>\n    }\n\n    renderWithConfig(<Page />)\n\n    await screen.findByText('hello,')\n    expect(fetch).not.toHaveBeenCalled()\n  })\n\n  it('should call fetch function when revalidateOnMount is false and key has been changed', async () => {\n    const fetch = jest.fn(() => 'SWR')\n\n    function Page() {\n      const [key, setKey] = useState(createKey())\n      const { data } = useSWR(key, fetch, {\n        revalidateOnMount: false\n      })\n      return <div onClick={() => setKey(createKey)}>hello,{data}</div>\n    }\n\n    renderWithConfig(<Page />)\n\n    await screen.findByText('hello,')\n    expect(fetch).not.toHaveBeenCalled()\n\n    // the key has been changed\n    fireEvent.click(screen.getByText('hello,'))\n\n    await screen.findByText('hello,SWR')\n  })\n\n  it('should call fetch function when revalidateOnMount is true even if fallbackData is set', async () => {\n    const fetch = jest.fn(() => 'SWR')\n\n    const key = createKey()\n    function Page() {\n      const { data } = useSWR(key, fetch, {\n        revalidateOnMount: true,\n        fallbackData: 'gab'\n      })\n      return <div>hello, {data}</div>\n    }\n\n    renderWithConfig(<Page />)\n    screen.getByText('hello, gab')\n\n    await screen.findByText('hello, SWR')\n    expect(fetch).toHaveBeenCalled()\n  })\n\n  it('initial loading state should be false when revalidation is disabled with fallbackData', async () => {\n    const fetch = jest.fn(() => 'SWR')\n\n    const key = createKey()\n    function Page() {\n      const { data, isLoading, isValidating } = useSWR(key, fetch, {\n        revalidateIfStale: false,\n        revalidateOnFocus: false,\n        revalidateOnReconnect: false,\n        fallbackData: 'Fallback'\n      })\n      return (\n        <div>\n          {data}, {isLoading.toString()} , {isValidating.toString()}\n        </div>\n      )\n    }\n\n    renderWithConfig(<Page />)\n    screen.getByText('Fallback, false , false')\n    expect(fetch).not.toHaveBeenCalled()\n  })\n\n  it('should dedupe requests by default', async () => {\n    const fetcher = jest.fn(() => createResponse('SWR'))\n\n    const key = createKey()\n    function Page() {\n      const { data: v1 } = useSWR(key, fetcher)\n      const { data: v2 } = useSWR(key, fetcher)\n      return (\n        <div>\n          {v1}, {v2}\n        </div>\n      )\n    }\n\n    renderWithConfig(<Page />)\n    screen.getByText(',')\n\n    await screen.findByText('SWR, SWR')\n    expect(fetcher).toHaveBeenCalledTimes(1)\n  })\n\n  it('should trigger the onSuccess event', async () => {\n    let SWRData = null\n    const key = createKey()\n    function Page() {\n      const { data } = useSWR(key, () => createResponse('SWR'), {\n        onSuccess: _data => (SWRData = _data)\n      })\n      return <div>hello, {data}</div>\n    }\n\n    renderWithConfig(<Page />)\n    screen.getByText('hello,')\n\n    await screen.findByText('hello, SWR')\n    expect(SWRData).toEqual('SWR')\n  })\n\n  it('should broadcast data', async () => {\n    let cnt = 0\n\n    const key = createKey()\n    function Block() {\n      const { data } = useSWR(key, () => cnt++, {\n        refreshInterval: 100,\n        // need to turn of deduping otherwise\n        // refreshing will be ignored\n        dedupingInterval: 10\n      })\n      return <>{data}</>\n    }\n    function Page() {\n      return (\n        <>\n          <Block /> <Block /> <Block />\n        </>\n      )\n    }\n\n    renderWithConfig(<Page />)\n\n    await act(() => sleep(50))\n    screen.getByText('0 0 0')\n\n    await act(() => sleep(100))\n    screen.getByText('1 1 1')\n\n    await act(() => sleep(100))\n    screen.getByText('2 2 2')\n  })\n\n  it('should broadcast error', async () => {\n    let cnt = 0\n\n    const key = createKey()\n    function Block() {\n      const { data, error } = useSWR(\n        key,\n        () => {\n          if (cnt === 2) throw new Error('err')\n          return cnt++\n        },\n        {\n          refreshInterval: 100,\n          // need to turn of deduping otherwise\n          // refreshing will be ignored\n          dedupingInterval: 10\n        }\n      )\n      if (error) return error.message\n      return <>{data}</>\n    }\n    function Page() {\n      return (\n        <>\n          <Block /> <Block /> <Block />\n        </>\n      )\n    }\n\n    renderWithConfig(<Page />)\n\n    await act(() => sleep(50))\n    screen.getByText('0 0 0')\n\n    await act(() => sleep(100))\n    screen.getByText('1 1 1')\n\n    await act(() => sleep(100))\n    screen.getByText('err err err')\n  })\n\n  it('should broadcast isValidating', async () => {\n    const key = createKey()\n    function useBroadcast3() {\n      const { isValidating, mutate } = useSWR(key, () => sleep(100), {\n        // need to turn of deduping otherwise\n        // revalidating will be ignored\n        dedupingInterval: 10\n      })\n      return { isValidating, mutate }\n    }\n    function Initiator() {\n      const { isValidating, mutate } = useBroadcast3()\n      useEffect(() => {\n        const timeout = setTimeout(() => {\n          mutate()\n        }, 200)\n        return () => clearTimeout(timeout)\n        // the revalidate function is always the same reference because the key of the useSWR is static (broadcast-3)\n        // eslint-disable-next-line react-hooks/exhaustive-deps\n      }, [])\n      return <>{isValidating ? 'true' : 'false'}</>\n    }\n    function Consumer() {\n      const { isValidating } = useBroadcast3()\n      return <>{isValidating ? 'true' : 'false'}</>\n    }\n    function Page() {\n      return (\n        <>\n          <Initiator /> <Consumer /> <Consumer />\n        </>\n      )\n    }\n\n    renderWithConfig(<Page />)\n    screen.getByText('true true true')\n\n    await act(() => sleep(150))\n    screen.getByText('false false false')\n\n    await act(() => sleep(100))\n    screen.getByText('true true true')\n\n    await act(() => sleep(100))\n    screen.getByText('false false false')\n  })\n\n  it('should accept object args', async () => {\n    const obj = { v: 'hello' }\n    const arr = ['world']\n\n    const key1 = createKey()\n    const key2 = createKey()\n    function Page() {\n      const { data: v1 } = useSWR(\n        [key1, obj, arr],\n        ([a, b, c]) => a + b.v + c[0]\n      )\n\n      // reuse the cache\n      const { data: v2 } = useSWR([key1, obj, arr], () => 'not called!')\n\n      // different object\n      const { data: v3 } = useSWR(\n        [key2, obj, 'world'],\n        ([a, b, c]) => a + b.v + c\n      )\n\n      return (\n        <div>\n          {v1}, {v2}, {v3}\n        </div>\n      )\n    }\n\n    renderWithConfig(<Page />)\n\n    await screen.findByText(\n      `${key1}helloworld, ${key1}helloworld, ${key2}helloworld`\n    )\n  })\n\n  it('should accept function returning args', async () => {\n    const obj = { v: 'hello' }\n    const arr = ['world']\n\n    const key = createKey()\n    function Page() {\n      const { data } = useSWR(\n        () => [key, obj, arr],\n        ([a, b, c]) => a + b.v + c[0]\n      )\n\n      return <div>{data}</div>\n    }\n\n    renderWithConfig(<Page />)\n\n    await screen.findByText(`${key}helloworld`)\n  })\n\n  it('should accept initial data', async () => {\n    const fetcher = jest.fn(() => 'SWR')\n\n    const key = createKey()\n    function Page() {\n      const { data } = useSWR(key, fetcher, {\n        fallbackData: 'Initial'\n      })\n      return <div>hello, {data}</div>\n    }\n\n    renderWithConfig(<Page />)\n\n    await screen.findByText('hello, Initial')\n    expect(fetcher).not.toHaveBeenCalled()\n  })\n\n  it('should revalidate even if fallbackData is provided', async () => {\n    const fetcher = key => createResponse(key, { delay: 50 })\n\n    const initialKey = createKey()\n    const updatedKey = createKey()\n    function Page() {\n      const [key, setKey] = useState(initialKey)\n      const { data } = useSWR(key, fetcher, {\n        fallbackData: 'Initial'\n      })\n      return (\n        <div onClick={() => setKey(updatedKey)}>\n          {data ? `hello, ${data}` : 'loading'}\n        </div>\n      )\n    }\n\n    renderWithConfig(<Page />)\n\n    // render with the initial data\n    await screen.findByText('hello, Initial')\n\n    await waitForNextTick()\n    fireEvent.focus(window)\n\n    await screen.findByText(`hello, ${initialKey}`)\n\n    // change the key\n    await waitForNextTick()\n    fireEvent.click(screen.getByText(`hello, ${initialKey}`))\n\n    // a request is still in flight\n    await act(() => sleep(10))\n    // while validating, SWR returns the fallbackData\n    // https://github.com/vercel/swr/pull/961/files#r588928241\n    screen.getByText('hello, Initial')\n\n    // render with data the fetcher returns\n    await screen.findByText(`hello, ${updatedKey}`)\n  })\n\n  it('should set config as second parameter', async () => {\n    const fetcher = jest.fn(() => 'SWR')\n\n    const key = createKey()\n    function Page() {\n      const { data } = useSWR(key, {\n        fetcher\n      })\n\n      return <div>hello, {data}</div>\n    }\n\n    renderWithConfig(<Page />)\n    screen.getByText('hello,')\n    expect(fetcher).toHaveBeenCalled()\n    await screen.findByText('hello, SWR')\n  })\n\n  it('should revalidate on mount after dedupingInterval', async () => {\n    const key = createKey()\n    let cnt = 0\n\n    function Foo() {\n      const { data } = useSWR(key, () => 'data: ' + cnt++, {\n        dedupingInterval: 0\n      })\n      return <>{data}</>\n    }\n\n    function Page() {\n      const [showFoo, setShowFoo] = React.useState(true)\n      return (\n        <>\n          {showFoo ? <Foo /> : null}\n          <button onClick={() => setShowFoo(!showFoo)}>toggle</button>\n        </>\n      )\n    }\n\n    renderWithConfig(<Page />)\n    await waitForNextTick()\n    screen.getByText('data: 0')\n    fireEvent.click(screen.getByText('toggle'))\n    await waitForNextTick()\n    fireEvent.click(screen.getByText('toggle'))\n    await act(() => sleep(20))\n    screen.getByText('data: 1')\n  })\n\n  it('Nested SWR hook should only do loading once', async () => {\n    const key = createKey()\n    let count = 0\n    const ChildComponent = () => {\n      const { data } = useSWR(key, _ => createResponse(_, { delay: 100 }))\n      return <div id=\"child\">{data}</div>\n    }\n    const NestedRender = () => {\n      const { data, isValidating } = useSWR(key, _ =>\n        createResponse(_, { delay: 50 })\n      )\n      if (isValidating) {\n        return <div>loading</div>\n      }\n      return (\n        <div>\n          <div id=\"parent\">{data}</div>\n          <ChildComponent />\n        </div>\n      )\n    }\n    const Page = () => (\n      <Profiler\n        id={key}\n        onRender={() => {\n          count += 1\n        }}\n      >\n        <NestedRender />\n      </Profiler>\n    )\n    renderWithConfig(<Page />)\n    await screen.findByText(`loading`)\n    await screen.findAllByText(key)\n    await act(() => sleep(150))\n    expect(count).toBe(2)\n  })\n\n  // Test for https://swr.vercel.app/docs/advanced/performance#dependency-collection\n  it('should render four times in the worst case', async () => {\n    let isFirstFetch = true\n    const fetcher = async () => {\n      if (isFirstFetch) {\n        isFirstFetch = false\n        throw new Error('error')\n      }\n      return 'value'\n    }\n    const key = createKey()\n\n    const logs = []\n\n    function Page() {\n      const { data, error, isLoading, isValidating } = useSWR(key, fetcher, {\n        errorRetryInterval: 10\n      })\n      logs.push({\n        data,\n        error,\n        isLoading,\n        isValidating\n      })\n      if (isLoading) return <p>loading</p>\n      return <p>data:{data}</p>\n    }\n\n    renderWithConfig(<Page />)\n    await screen.findByText('data:value')\n\n    expect(logs).toMatchInlineSnapshot(`\n      [\n        {\n          \"data\": undefined,\n          \"error\": undefined,\n          \"isLoading\": true,\n          \"isValidating\": true,\n        },\n        {\n          \"data\": undefined,\n          \"error\": [Error: error],\n          \"isLoading\": false,\n          \"isValidating\": false,\n        },\n        {\n          \"data\": undefined,\n          \"error\": [Error: error],\n          \"isLoading\": true,\n          \"isValidating\": true,\n        },\n        {\n          \"data\": \"value\",\n          \"error\": undefined,\n          \"isLoading\": false,\n          \"isValidating\": false,\n        },\n      ]\n    `)\n  })\n\n  // Test for https://swr.vercel.app/docs/advanced/performance#dependency-collection\n  it('should render only two times in the best case', async () => {\n    let isFirstFetch = true\n    const fetcher = async () => {\n      if (isFirstFetch) {\n        isFirstFetch = false\n        throw new Error('error')\n      }\n      return 'value'\n    }\n    const key = createKey()\n\n    const logs = []\n\n    function Page() {\n      const { data } = useSWR(key, fetcher, {\n        errorRetryInterval: 10\n      })\n      logs.push({ data })\n      if (!data) return <p>loading</p>\n      return <p>data:{data}</p>\n    }\n\n    renderWithConfig(<Page />)\n    await screen.findByText('data:value')\n\n    expect(logs).toMatchInlineSnapshot(`\n      [\n        {\n          \"data\": undefined,\n        },\n        {\n          \"data\": \"value\",\n        },\n      ]\n    `)\n  })\n\n  // Test for https://github.com/vercel/swr/issues/2446\n  it('should return latest data synchronously after accessing getter', async () => {\n    const fetcher = async () => {\n      await sleep(10)\n      return 'value'\n    }\n    const key = createKey()\n\n    const logs = []\n\n    function Page() {\n      const swr = useSWR(key, fetcher)\n      const [show, setShow] = useState(false)\n      useEffect(() => {\n        setTimeout(() => {\n          setShow(true)\n        }, 100)\n      }, [])\n      if (!show) return null\n      logs.push(swr.data)\n      return <p>data:{swr.data}</p>\n    }\n\n    renderWithConfig(<Page />)\n    await screen.findByText('data:value')\n    expect(logs).toMatchInlineSnapshot(`\n      [\n        \"value\",\n      ]\n    `)\n  })\n})\n"
  },
  {
    "path": "test/use-swr-key.test.tsx",
    "content": "import { fireEvent, screen } from '@testing-library/react'\nimport React, { useState, useEffect, act } from 'react'\nimport useSWR from 'swr'\nimport { createKey, createResponse, renderWithConfig, sleep } from './utils'\n\ndescribe('useSWR - key', () => {\n  it('should respect requests after key has changed', async () => {\n    let rerender\n\n    const baseKey = createKey()\n    function Page() {\n      const [mounted, setMounted] = useState(0)\n      const key = `${baseKey}-${mounted ? 'short' : 'long'}`\n      const { data } = useSWR(key, () => {\n        if (mounted) {\n          return createResponse('short request', { delay: 50 })\n        }\n        return createResponse('long request', { delay: 100 })\n      })\n      useEffect(() => setMounted(1), [])\n      rerender = setMounted\n\n      return <div>data:{data}</div>\n    }\n\n    renderWithConfig(<Page />)\n    screen.getByText('data:')\n\n    await screen.findByText('data:short request')\n\n    await act(() => sleep(100)) // wait 100ms until \"long request\" finishes\n    screen.getByText('data:short request') // should be \"short request\" still\n\n    // manually trigger a re-render from outside\n    // this triggers a re-render and a read access to `swr.data`\n    // but the result should still be \"short request\"\n    act(() => rerender(x => x + 1))\n    screen.getByText('data:short request')\n  })\n\n  it('should render undefined after key has changed', async () => {\n    const baseKey = createKey()\n    function Page() {\n      const [mounted, setMounted] = useState(false)\n      const key = `${baseKey}-${mounted ? '1' : '0'}`\n      const { data } = useSWR(key, k => createResponse(k, { delay: 100 }))\n      useEffect(() => {\n        setTimeout(() => setMounted(true), 200)\n      }, [])\n      return <div>data:{data}</div>\n    }\n\n    //    time     data       key\n    // -> 0        undefined, '0'\n    // -> 100      0,         '0'\n    // -> 200      undefined, '1' <- this state is required; we can't show 0 here\n    // -> 300      1,         '1'\n    renderWithConfig(<Page />)\n    screen.getByText('data:') // undefined, time=0\n    await act(() => sleep(150))\n    screen.getByText(`data:${baseKey}-0`) // 0, time=150\n    await act(() => sleep(100))\n    screen.getByText('data:') // undefined, time=250\n    await act(() => sleep(100))\n    screen.getByText(`data:${baseKey}-1`) // 1, time=350\n  })\n\n  it('should return undefined after key change when fetcher is synchronized', async () => {\n    const samples = {\n      '1': 'a',\n      '2': 'b',\n      '3': 'c'\n    }\n\n    const baseKey = createKey()\n    function Page() {\n      const [sampleKey, setKey] = React.useState(1)\n      const { data } = useSWR(\n        `${baseKey}-${sampleKey}`,\n        key => samples[key.replace(`${baseKey}-`, '')]\n      )\n      return (\n        <div\n          onClick={() => {\n            setKey(sampleKey + 1)\n          }}\n        >\n          hello, {sampleKey}:{data}\n        </div>\n      )\n    }\n\n    renderWithConfig(<Page />)\n    screen.getByText('hello, 1:')\n\n    await screen.findByText('hello, 1:a')\n\n    fireEvent.click(screen.getByText('hello, 1:a'))\n    // first, rerender on key change\n    screen.getByText('hello, 2:')\n\n    await screen.findByText('hello, 2:b')\n  })\n\n  it('should revalidate if a function key changes identity', async () => {\n    const closureFunctions: { [key: string]: () => string } = {}\n\n    const baseKey = createKey()\n    const closureFactory = id => {\n      if (closureFunctions[id]) return closureFunctions[id]\n      closureFunctions[id] = () => `${baseKey}-${id}`\n      return closureFunctions[id]\n    }\n\n    let updateId\n\n    const fetcher = (key: string) => Promise.resolve(key)\n\n    function Page() {\n      const [id, setId] = React.useState('first')\n      updateId = setId\n      const fnWithClosure = closureFactory(id)\n      const { data } = useSWR(fnWithClosure, fetcher)\n\n      return <div>{data}</div>\n    }\n\n    renderWithConfig(<Page />)\n    await screen.findByText(`${baseKey}-first`)\n\n    act(() => updateId('first'))\n    await screen.findByText(`${baseKey}-first`)\n\n    act(() => updateId('second'))\n    await screen.findByText(`${baseKey}-second`)\n  })\n\n  it('should not fetch if the function key throws an error', async () => {\n    let value = 0\n    const fetcher = jest.fn(() => value++)\n    const key = () => {\n      throw new Error('error')\n    }\n\n    function Page() {\n      const { data } = useSWR(key, fetcher)\n      return <div>{`key-${data}`}</div>\n    }\n\n    renderWithConfig(<Page />)\n    await screen.findByText(`key-undefined`)\n    expect(fetcher).toHaveBeenCalledTimes(0)\n  })\n\n  it('should cleanup state when key turns to empty', async () => {\n    const key = createKey()\n    function Page() {\n      const [cnt, setCnt] = useState(1)\n      const { isValidating } = useSWR(cnt === -1 ? '' : `${key}-${cnt}`, () =>\n        createResponse('', { delay: 100 })\n      )\n\n      return (\n        <div onClick={() => setCnt(cnt == 2 ? -1 : cnt + 1)}>\n          {isValidating ? 'true' : 'false'}\n        </div>\n      )\n    }\n\n    renderWithConfig(<Page />)\n    screen.getByText('true')\n\n    fireEvent.click(screen.getByText('true'))\n    await act(() => sleep(10))\n    screen.getByText('true')\n\n    fireEvent.click(screen.getByText('true'))\n    await act(() => sleep(10))\n    screen.getByText('false')\n  })\n\n  it('should keep data in sync when key updates', async () => {\n    const fetcher = () => createResponse('test', { delay: 100 })\n    const values = []\n\n    const updatedKey = createKey()\n    function Page() {\n      const [key, setKey] = useState(null)\n\n      const { data: v1 } = useSWR(key, fetcher)\n      const { data: v2 } = useSWR(key, fetcher)\n\n      values.push([v1, v2])\n\n      return <button onClick={() => setKey(updatedKey)}>update key</button>\n    }\n\n    renderWithConfig(<Page />)\n    screen.getByText('update key')\n\n    fireEvent.click(screen.getByText('update key'))\n    await act(() => sleep(120))\n\n    // All values should be equal because they're sharing the same key\n    expect(values.some(([a, b]) => a !== b)).toBeFalsy()\n  })\n\n  it('should support object as the key and deep compare', async () => {\n    const fetcher = jest.fn(() => 'data')\n    function Page() {\n      const { data: v1 } = useSWR({ foo: { bar: 1 } }, fetcher)\n      const { data: v2 } = useSWR({ foo: { bar: 1 } }, fetcher)\n\n      return (\n        <div>\n          {v1},{v2}\n        </div>\n      )\n    }\n\n    renderWithConfig(<Page />)\n    await screen.findByText('data,data')\n\n    // Only 1 request since the keys are the same.\n    expect(fetcher).toHaveBeenCalledTimes(1)\n  })\n})\n"
  },
  {
    "path": "test/use-swr-laggy.test.tsx",
    "content": "import { screen, fireEvent } from '@testing-library/react'\nimport { useState, act } from 'react'\nimport useSWR from 'swr'\nimport useSWRInfinite from 'swr/infinite'\n\nimport { createKey, createResponse, renderWithConfig, sleep } from './utils'\n\ndescribe('useSWR - keep previous data', () => {\n  it('should keep previous data when key changes when `keepPreviousData` is enabled', async () => {\n    const loggedData = []\n    const fetcher = k => createResponse(k, { delay: 50 })\n    function App() {\n      const [key, setKey] = useState(createKey())\n      const { data: laggedData } = useSWR(key, fetcher, {\n        keepPreviousData: true\n      })\n      loggedData.push([key, laggedData])\n      return <button onClick={() => setKey(createKey())}>change key</button>\n    }\n\n    renderWithConfig(<App />)\n    await act(() => sleep(100))\n    fireEvent.click(screen.getByText('change key'))\n    await act(() => sleep(100))\n\n    const key1 = loggedData[0][0]\n    const key2 = loggedData[2][0]\n    expect(loggedData).toEqual([\n      [key1, undefined],\n      [key1, key1],\n      [key2, key1],\n      [key2, key2]\n    ])\n  })\n\n  it('should keep previous data when sharing the cache', async () => {\n    const loggedData = []\n    const fetcher = k => createResponse(k, { delay: 50 })\n    function App() {\n      const [key, setKey] = useState(createKey())\n\n      const { data } = useSWR(key, fetcher)\n      const { data: laggedData } = useSWR(key, fetcher, {\n        keepPreviousData: true\n      })\n\n      loggedData.push([key, data, laggedData])\n      return <button onClick={() => setKey(createKey())}>change key</button>\n    }\n\n    renderWithConfig(<App />)\n    await act(() => sleep(100))\n    fireEvent.click(screen.getByText('change key'))\n    await act(() => sleep(100))\n\n    const key1 = loggedData[0][0]\n    const key2 = loggedData[2][0]\n    expect(loggedData).toEqual([\n      [key1, undefined, undefined],\n      [key1, key1, key1],\n      [key2, undefined, key1],\n      [key2, key2, key2]\n    ])\n  })\n\n  it('should keep previous data even if there is fallback data', async () => {\n    const loggedData = []\n    const fetcher = k => createResponse(k, { delay: 50 })\n    function App() {\n      const [key, setKey] = useState(createKey())\n\n      const { data } = useSWR(key, fetcher, {\n        fallbackData: 'fallback'\n      })\n      const { data: laggedData } = useSWR(key, fetcher, {\n        keepPreviousData: true,\n        fallbackData: 'fallback'\n      })\n\n      loggedData.push([key, data, laggedData])\n      return <button onClick={() => setKey(createKey())}>change key</button>\n    }\n\n    renderWithConfig(<App />)\n    await act(() => sleep(100))\n    fireEvent.click(screen.getByText('change key'))\n    await act(() => sleep(100))\n\n    const key1 = loggedData[0][0]\n    const key2 = loggedData[2][0]\n    expect(loggedData).toEqual([\n      [key1, 'fallback', 'fallback'],\n      [key1, key1, key1],\n      [key2, 'fallback', key1],\n      [key2, key2, key2]\n    ])\n  })\n\n  it('should always return the latest data', async () => {\n    const loggedData = []\n    const fetcher = k => createResponse(k, { delay: 50 })\n    function App() {\n      const [key, setKey] = useState(createKey())\n      const { data: laggedData, mutate } = useSWR(key, fetcher, {\n        keepPreviousData: true\n      })\n      loggedData.push([key, laggedData])\n      return (\n        <>\n          <button onClick={() => setKey(createKey())}>change key</button>\n          <button onClick={() => mutate('mutate')}>mutate</button>\n        </>\n      )\n    }\n\n    renderWithConfig(<App />)\n    await act(() => sleep(100))\n    fireEvent.click(screen.getByText('change key'))\n    await act(() => sleep(100))\n    fireEvent.click(screen.getByText('mutate'))\n    await act(() => sleep(100))\n\n    const key1 = loggedData[0][0]\n    const key2 = loggedData[2][0]\n    expect(loggedData).toEqual([\n      [key1, undefined],\n      [key1, key1],\n      [key2, key1],\n      [key2, key2],\n      [key2, 'mutate'],\n      [key2, key2]\n    ])\n  })\n\n  it('should keep previous data for the useSWRInfinite hook', async () => {\n    const loggedData = []\n    const fetcher = k => createResponse(k, { delay: 50 })\n    function App() {\n      const [key, setKey] = useState(createKey())\n\n      const { data } = useSWRInfinite(() => key, fetcher, {\n        keepPreviousData: true\n      })\n\n      loggedData.push([key, data])\n      return <button onClick={() => setKey(createKey())}>change key</button>\n    }\n\n    renderWithConfig(<App />)\n    await act(() => sleep(100))\n    fireEvent.click(screen.getByText('change key'))\n    await act(() => sleep(100))\n\n    const key1 = loggedData[0][0]\n    const key2 = loggedData[2][0]\n    expect(loggedData).toEqual([\n      [key1, undefined],\n      [key1, [key1]],\n      [key2, [key1]],\n      [key2, [key2]]\n    ])\n  })\n\n  it('should support changing the `keepPreviousData` option', async () => {\n    const loggedData = []\n    const fetcher = k => createResponse(k, { delay: 50 })\n    let keepPreviousData = false\n    function App() {\n      const [key, setKey] = useState(createKey())\n      const { data: laggedData } = useSWR(key, fetcher, {\n        keepPreviousData\n      })\n      loggedData.push([key, laggedData])\n      return <button onClick={() => setKey(createKey())}>change key</button>\n    }\n\n    renderWithConfig(<App />)\n    await act(() => sleep(100))\n    fireEvent.click(screen.getByText('change key'))\n    await act(() => sleep(100))\n    keepPreviousData = true\n    fireEvent.click(screen.getByText('change key'))\n    await act(() => sleep(100))\n\n    const key1 = loggedData[0][0]\n    const key2 = loggedData[2][0]\n    const key3 = loggedData[4][0]\n    expect(loggedData).toEqual([\n      [key1, undefined],\n      [key1, key1],\n      [key2, undefined],\n      [key2, key2],\n      [key3, key2],\n      [key3, key3]\n    ])\n  })\n\n  // https://github.com/vercel/swr/issues/2128\n  it('should re-render when returned data and fallbackData is the same and keepPreviousData is enabled', async () => {\n    const fallbackData = 'initial'\n    const fetcher = k => createResponse(k, { delay: 50 })\n    const keys = ['initial', 'updated']\n    function App() {\n      const [count, setCount] = useState(0)\n      const { data } = useSWR(keys[count % 2 === 0 ? 0 : 1], fetcher, {\n        fallbackData,\n        keepPreviousData: true,\n        revalidateOnMount: false,\n        revalidateOnFocus: false\n      })\n      return (\n        <>\n          <button onClick={() => setCount(c => c + 1)}>change key</button>\n          <div>{data}</div>\n        </>\n      )\n    }\n\n    renderWithConfig(<App />)\n    // fallbackData\n    screen.getByText('initial')\n\n    fireEvent.click(screen.getByText('change key'))\n    await act(() => sleep(10))\n    // previous data\n    screen.getByText('initial')\n    await act(() => sleep(100))\n    screen.getByText('updated')\n\n    fireEvent.click(screen.getByText('change key'))\n    await act(() => sleep(10))\n    // previous data\n    screen.getByText('updated')\n    await act(() => sleep(100))\n    screen.getByText('initial')\n  })\n\n  it('should work keepPreviousData without changing th key', async () => {\n    const key = createKey()\n    let counter = 0\n    const fetcher = () => createResponse(++counter, { delay: 50 })\n    function App() {\n      const { data, mutate } = useSWR(key, fetcher)\n      const { data: laggedData } = useSWR(key, fetcher, {\n        keepPreviousData: true\n      })\n\n      return (\n        <>\n          <button onClick={() => mutate(undefined)}>mutate</button>\n          <div>\n            data:{data},laggy:{laggedData}\n          </div>\n        </>\n      )\n    }\n\n    renderWithConfig(<App />)\n    screen.getByText('data:,laggy:')\n    await act(() => sleep(100))\n    screen.getByText('data:1,laggy:1')\n    fireEvent.click(screen.getByText('mutate'))\n    // previous data\n    screen.getByText('data:,laggy:1')\n    await act(() => sleep(100))\n    screen.getByText('data:2,laggy:2')\n  })\n})\n"
  },
  {
    "path": "test/use-swr-legacy-react.test.tsx",
    "content": "// This test case covers special environments such as React <= 17.\n\nimport { screen, render, fireEvent } from '@testing-library/react'\nimport { act } from 'react'\n\n// https://github.com/jestjs/jest/issues/11471\njest.mock('react', () => jest.requireActual('react'))\n\nasync function withLegacyReact(runner: () => Promise<void>) {\n  await jest.isolateModulesAsync(async () => {\n    await runner()\n  })\n}\n\ndescribe('useSWR - legacy React', () => {\n  ;(process.env.__SWR_TEST_BUILD ? it.skip : it)(\n    'should enable the IS_REACT_LEGACY flag - startTransition',\n    async () => {\n      await withLegacyReact(async () => {\n        // Test mutation and trigger\n        const useSWRMutation = (await import('swr/mutation')).default\n\n        const waitForNextTick = () =>\n          act(() => new Promise(resolve => setTimeout(resolve, 1)))\n        const key = Math.random().toString()\n\n        function Page() {\n          const { data, trigger } = useSWRMutation(key, () => 'data')\n          return <button onClick={() => trigger()}>{data || 'pending'}</button>\n        }\n\n        render(<Page />)\n\n        // mount\n        await screen.findByText('pending')\n\n        fireEvent.click(screen.getByText('pending'))\n        await waitForNextTick()\n\n        screen.getByText('data')\n      })\n    }\n  )\n\n  // https://github.com/vercel/swr/blob/cfcfa9e320a59742d41a77e52003127b04378c4f/src/core/use-swr.ts#L345\n  ;(process.env.__SWR_TEST_BUILD ? it.skip : it)(\n    'should enable the IS_REACT_LEGACY flag - unmount check',\n    async () => {\n      await withLegacyReact(async () => {\n        const useSWR = (await import('swr')).default\n\n        const key = Math.random().toString()\n\n        function Page() {\n          // No fallback data\n          const { data } = useSWR(\n            key,\n            () =>\n              new Promise<string>(resolve =>\n                setTimeout(() => resolve('data'), 100)\n              ),\n            {\n              loadingTimeout: 10\n            }\n          )\n          return <p>{data || 'pending'}</p>\n        }\n\n        render(<Page />)\n\n        await screen.findByText('data')\n      })\n    }\n  )\n})\n"
  },
  {
    "path": "test/use-swr-loading.test.tsx",
    "content": "import { screen, fireEvent } from '@testing-library/react'\nimport React, { useEffect, act } from 'react'\nimport useSWR from 'swr'\nimport {\n  createResponse,\n  createKey,\n  sleep,\n  renderWithConfig,\n  nextTick,\n  executeWithoutBatching\n} from './utils'\n\ndescribe('useSWR - loading', () => {\n  it('should return validating state', async () => {\n    let renderCount = 0\n    const key = createKey()\n    function Page() {\n      const { data, isValidating } = useSWR(key, () => createResponse('data'))\n      renderCount++\n      return (\n        <div>\n          hello, {data}, {isValidating ? 'validating' : 'ready'}\n        </div>\n      )\n    }\n\n    renderWithConfig(<Page />)\n    screen.getByText('hello, , validating')\n\n    await screen.findByText('hello, data, ready')\n    //    data       isValidating\n    // -> undefined, true\n    // -> data,      false\n    expect(renderCount).toEqual(2)\n  })\n\n  it('should return loading state', async () => {\n    let renderCount = 0\n    const key = createKey()\n    function Page() {\n      const { data, isLoading } = useSWR(key, () => createResponse('data'))\n      renderCount++\n      return (\n        <div>\n          hello, {data}, {isLoading ? 'loading' : 'ready'}\n        </div>\n      )\n    }\n\n    renderWithConfig(<Page />)\n    screen.getByText('hello, , loading')\n\n    await screen.findByText('hello, data, ready')\n    //    data       isLoading\n    // -> undefined, true\n    // -> data,      false\n    expect(renderCount).toEqual(2)\n  })\n\n  it('should avoid extra rerenders', async () => {\n    let renderCount = 0\n    const key = createKey()\n    function Page() {\n      // we never access `isValidating`, so it will not trigger rerendering\n      const { data } = useSWR(key, () => createResponse('data'))\n      renderCount++\n      return <div>hello, {data}</div>\n    }\n\n    renderWithConfig(<Page />)\n\n    await screen.findByText('hello, data')\n    //    data\n    // -> undefined\n    // -> data\n    expect(renderCount).toEqual(2)\n  })\n\n  it('should avoid extra rerenders while fetching', async () => {\n    let renderCount = 0,\n      dataLoaded = false\n\n    const key = createKey()\n    function Page() {\n      // we never access anything\n      useSWR(key, async () => {\n        const res = await createResponse('data')\n        dataLoaded = true\n        return res\n      })\n      renderCount++\n      return <div>hello</div>\n    }\n\n    renderWithConfig(<Page />)\n    screen.getByText('hello')\n\n    await executeWithoutBatching(() => sleep(100)) // wait\n    // it doesn't re-render, but fetch was triggered\n    expect(renderCount).toEqual(1)\n    expect(dataLoaded).toEqual(true)\n  })\n\n  it('should avoid extra rerenders when the fallback is the same as cache', async () => {\n    let renderCount = 0,\n      initialDataLoaded = false,\n      mutationDataLoaded = false\n\n    const key = createKey()\n    function Page() {\n      const { data, mutate } = useSWR(\n        key,\n        async () => {\n          const res = await createResponse({ greeting: 'hello' })\n          initialDataLoaded = true\n          return res\n        },\n        { fallbackData: { greeting: 'hello' } }\n      )\n\n      useEffect(() => {\n        const timeout = setTimeout(\n          () =>\n            mutate(async () => {\n              const res = await createResponse({ greeting: 'hello' })\n              mutationDataLoaded = true\n              return res\n            }),\n          200\n        )\n\n        return () => clearTimeout(timeout)\n      }, [mutate])\n\n      renderCount++\n      return <div>{data?.greeting}</div>\n    }\n\n    renderWithConfig(<Page />)\n    screen.getByText('hello')\n\n    await act(() => sleep(1000)) // wait\n    // it doesn't re-render, but fetch was triggered\n    expect(initialDataLoaded).toEqual(true)\n    expect(mutationDataLoaded).toEqual(true)\n    expect(renderCount).toEqual(1)\n  })\n\n  it('should return enumerable object', async () => {\n    // If the returned object is enumerable, we can use the spread operator\n    // to deconstruct all the keys.\n\n    function Page() {\n      const swr = useSWR(createKey())\n      return (\n        <div>\n          {Object.keys({ ...swr })\n            .sort()\n            .join(',')}\n        </div>\n      )\n    }\n\n    renderWithConfig(<Page />)\n    screen.getByText('data,error,isLoading,isValidating,mutate')\n  })\n\n  it('should sync validating states', async () => {\n    const key = createKey()\n    const fetcher = jest.fn()\n\n    function Foo() {\n      const { isValidating } = useSWR(key, async () => {\n        fetcher()\n        return 'foo'\n      })\n      return isValidating ? <>validating</> : <>stopped</>\n    }\n\n    function Page() {\n      return (\n        <>\n          <Foo />,<Foo />\n        </>\n      )\n    }\n\n    renderWithConfig(<Page />)\n    screen.getByText('validating,validating')\n    await nextTick()\n    screen.getByText('stopped,stopped')\n    expect(fetcher).toHaveBeenCalledTimes(1)\n  })\n\n  it('should sync all validating states if errored', async () => {\n    const key = createKey()\n\n    function Foo() {\n      const { isValidating } = useSWR(key, async () => {\n        throw new Error(key)\n      })\n\n      return isValidating ? <>validating</> : <>stopped</>\n    }\n\n    function Page() {\n      return (\n        <>\n          <Foo />,<Foo />\n        </>\n      )\n    }\n\n    renderWithConfig(<Page />)\n    screen.getByText('validating,validating')\n    await nextTick()\n    screen.getByText('stopped,stopped')\n  })\n\n  it('should sync all validating states if errored but paused', async () => {\n    const key = createKey()\n    let paused = false\n\n    function Foo() {\n      const { isValidating } = useSWR(key, {\n        isPaused: () => paused,\n        fetcher: async () => {\n          await sleep(50)\n          throw new Error(key)\n        },\n        dedupingInterval: 0,\n        errorRetryInterval: 50\n      })\n\n      return isValidating ? <>validating</> : <>stopped</>\n    }\n\n    function Page() {\n      const [mountSecondRequest, setMountSecondRequest] = React.useState(false)\n      return (\n        <>\n          <Foo />,{mountSecondRequest ? <Foo /> : null}\n          <br />\n          <button onClick={() => setMountSecondRequest(true)}>start</button>\n        </>\n      )\n    }\n\n    renderWithConfig(<Page />)\n    screen.getByText('validating,')\n    await executeWithoutBatching(() => sleep(70))\n    screen.getByText('stopped,')\n\n    fireEvent.click(screen.getByText('start'))\n    await screen.findByText('validating,validating')\n\n    // Pause before it resolves\n    paused = true\n    await executeWithoutBatching(() => sleep(50))\n\n    // They should both stop\n    screen.getByText('stopped,stopped')\n  })\n\n  it('should not trigger loading state when revalidating', async () => {\n    const key = createKey()\n    let renderCount = 0\n    function Page() {\n      const { isLoading, isValidating, mutate } = useSWR(key, () =>\n        createResponse('data', { delay: 10 })\n      )\n      renderCount++\n      return (\n        <div>\n          <div>\n            {isLoading ? 'loading' : 'ready'},\n            {isValidating ? 'validating' : 'ready'}\n          </div>\n          <button onClick={() => mutate()}>revalidate</button>\n        </div>\n      )\n    }\n\n    renderWithConfig(<Page />)\n    screen.getByText('loading,validating')\n    await screen.findByText('ready,ready')\n\n    fireEvent.click(screen.getByText('revalidate'))\n    screen.getByText('ready,validating')\n    await screen.findByText('ready,ready')\n\n    // isValidating: true -> false -> true -> false\n    expect(renderCount).toBe(4)\n  })\n\n  it('should trigger loading state when changing the key', async () => {\n    function Page() {\n      const [key, setKey] = React.useState(createKey)\n      const { isLoading, isValidating } = useSWR(key, () =>\n        createResponse('data', { delay: 10 })\n      )\n      return (\n        <div>\n          <div>\n            {isLoading ? 'loading' : 'ready'},\n            {isValidating ? 'validating' : 'ready'}\n          </div>\n          <button onClick={() => setKey(createKey())}>update key</button>\n        </div>\n      )\n    }\n\n    renderWithConfig(<Page />)\n    screen.getByText('loading,validating')\n    await screen.findByText('ready,ready')\n\n    fireEvent.click(screen.getByText('update key'))\n    screen.getByText('loading,validating')\n    await screen.findByText('ready,ready')\n  })\n  it('isLoading and isValidating should always respect cache value', async () => {\n    const key = createKey()\n    const Page = () => {\n      const { data } = useSWR(key, () =>\n        createResponse('result', { delay: 10 })\n      )\n      const { data: response } = useSWR(data, () =>\n        createResponse('data', { delay: 10 })\n      )\n      // eslint-disable-next-line react/display-name\n      const Component = ((_: any) => () => {\n        const {\n          data: result,\n          isLoading,\n          isValidating\n          // eslint-disable-next-line react-hooks/rules-of-hooks\n        } = useSWR(key, () => createResponse('result', { delay: 10 }))\n        return (\n          <div>{`result is ${\n            result ? result : 'null'\n          },${isLoading},${isValidating}`}</div>\n        )\n      })(response)\n      return <Component></Component>\n    }\n    renderWithConfig(<Page />)\n    screen.getByText('result is null,true,true')\n    await screen.findByText('result is result,false,false')\n  })\n\n  it('isLoading should be false when key is null', () => {\n    function Page() {\n      const { isLoading } = useSWR(null, () => 'data')\n      return <div>isLoading:{String(isLoading)}</div>\n    }\n\n    renderWithConfig(<Page />)\n    screen.getByText('isLoading:false')\n  })\n\n  it('isLoading should be false when the key function throws an error', () => {\n    function Page() {\n      const { isLoading } = useSWR(\n        () => {\n          throw new Error('error')\n        },\n        () => 'data'\n      )\n      return <div>isLoading:{String(isLoading)}</div>\n    }\n\n    renderWithConfig(<Page />)\n    screen.getByText('isLoading:false')\n  })\n})\n"
  },
  {
    "path": "test/use-swr-local-mutation.test.tsx",
    "content": "import { screen, fireEvent } from '@testing-library/react'\nimport { useEffect, useState, act } from 'react'\nimport useSWR, { mutate as globalMutate, useSWRConfig } from 'swr'\nimport useSWRInfinite from 'swr/infinite'\nimport { serialize } from 'swr/_internal'\nimport {\n  createResponse,\n  sleep,\n  nextTick,\n  createKey,\n  renderWithConfig,\n  renderWithGlobalCache,\n  executeWithoutBatching\n} from './utils'\n\ndescribe('useSWR - local mutation', () => {\n  it('should trigger revalidation programmatically', async () => {\n    let value = 0,\n      mutate\n    const key = createKey()\n    function Page() {\n      mutate = useSWRConfig().mutate\n      const { data } = useSWR(key, () => value++, {\n        dedupingInterval: 0\n      })\n      return <div>data: {data}</div>\n    }\n\n    renderWithConfig(<Page />)\n    // hydration\n    screen.getByText('data:')\n\n    // mount\n    await screen.findByText('data: 0')\n\n    act(() => {\n      // mutate and revalidate\n      mutate(key)\n    })\n    await screen.findByText('data: 1')\n  })\n\n  it('should share local state when no fetcher is specified', async () => {\n    const baseKey = createKey()\n    const useSharedState = (key, fallbackData) => {\n      const { data: state, mutate: setState } = useSWR(`${baseKey}--${key}`, {\n        fallbackData\n      })\n      return [state, setState]\n    }\n\n    function Page() {\n      const [name, setName] = useSharedState('name', 'huozhi')\n      const [job, setJob] = useSharedState('job', 'gardener')\n\n      return (\n        <span\n          onClick={() => {\n            setName('@huozhi')\n            setJob('chef')\n          }}\n        >\n          {`${name}:${job}`}\n        </span>\n      )\n    }\n    renderWithConfig(<Page />)\n    const root = screen.getByText('huozhi:gardener')\n    fireEvent.click(root)\n    await screen.findByText('@huozhi:chef')\n  })\n\n  it('should trigger revalidation programmatically within a dedupingInterval', async () => {\n    let value = 0,\n      mutate\n\n    const key = createKey()\n    function Page() {\n      mutate = useSWRConfig().mutate\n      const { data } = useSWR(key, () => value++, {\n        dedupingInterval: 2000\n      })\n      return <div>data: {data}</div>\n    }\n\n    renderWithConfig(<Page />)\n    // hydration\n    screen.getByText('data:')\n\n    // mount\n    await screen.findByText('data: 0')\n\n    act(() => {\n      // trigger revalidation\n      mutate(key)\n    })\n    await screen.findByText('data: 1')\n  })\n\n  it('should mutate the cache and revalidate', async () => {\n    let value = 0,\n      mutate\n\n    const key = createKey()\n    function Page() {\n      mutate = useSWRConfig().mutate\n      const { data } = useSWR(key, () => value++, {\n        dedupingInterval: 0\n      })\n      return <div>data: {data}</div>\n    }\n\n    renderWithConfig(<Page />)\n    // hydration\n    screen.getByText('data:')\n\n    // mount\n    await screen.findByText('data: 0')\n\n    act(() => {\n      // mutate and revalidate\n      mutate(key, 'mutate')\n    })\n    await screen.findByText('data: 1')\n  })\n\n  it('should dedupe extra requests after mutation', async () => {\n    let value = 0,\n      mutate\n\n    const key = createKey()\n    function Page() {\n      mutate = useSWRConfig().mutate\n      const { data } = useSWR(key, () => value++, {\n        dedupingInterval: 2000\n      })\n\n      useSWR(key, () => value++, {\n        dedupingInterval: 2000\n      })\n      return <div>data: {data}</div>\n    }\n\n    renderWithConfig(<Page />)\n    // hydration\n    screen.getByText('data:')\n\n    // mount\n    await screen.findByText('data: 0')\n    act(() => {\n      // mutate and revalidate\n      mutate(key)\n    })\n    await screen.findByText('data: 1')\n  })\n\n  it('should mutate the cache and revalidate in async', async () => {\n    let mutate\n    const key = createKey()\n    function Page() {\n      mutate = useSWRConfig().mutate\n      const { data } = useSWR(key, () => createResponse('truth'), {\n        dedupingInterval: 0\n      })\n      return <div>data: {data}</div>\n    }\n\n    renderWithConfig(<Page />)\n    // hydration\n    screen.getByText('data:')\n\n    // mount\n    await screen.findByText('data: truth')\n\n    act(() => {\n      // mutate and revalidate\n      mutate(key, 'local')\n    })\n\n    await screen.findByText('data: local')\n\n    // recovers\n    await screen.findByText('data: truth')\n  })\n\n  it('should support async mutation with promise', async () => {\n    let mutate\n    const key = createKey()\n    function Page() {\n      mutate = useSWRConfig().mutate\n      const { data } = useSWR(key, () => 0, {\n        dedupingInterval: 0\n      })\n      return <div>data: {data}</div>\n    }\n\n    renderWithConfig(<Page />)\n    // hydration\n    screen.getByText('data:')\n\n    // mount\n    await screen.findByText('data: 0')\n\n    await nextTick()\n    await act(() => {\n      // mutate and revalidate\n      return mutate(key, createResponse(999), false)\n    })\n    await screen.findByText('data: 999')\n  })\n\n  it('should support async mutation with async function', async () => {\n    let mutate\n    const key = createKey()\n    function Page() {\n      mutate = useSWRConfig().mutate\n      const { data } = useSWR(key, () => 0, {\n        dedupingInterval: 0\n      })\n      return <div>data: {data}</div>\n    }\n\n    renderWithConfig(<Page />)\n    // hydration\n    screen.getByText('data:')\n\n    // mount\n    await screen.findByText('data: 0')\n\n    await nextTick()\n    await act(() => {\n      // mutate and revalidate\n      return mutate(key, async () => createResponse(999), false)\n    })\n    await screen.findByText('data: 999')\n  })\n\n  it('should trigger on mutation without data', async () => {\n    let value = 0,\n      mutate\n\n    const key = createKey()\n    function Page() {\n      mutate = useSWRConfig().mutate\n      const { data } = useSWR(key, () => value++, {\n        dedupingInterval: 0\n      })\n      return <div>data: {data}</div>\n    }\n\n    renderWithConfig(<Page />)\n    // hydration\n    screen.getByText('data:')\n\n    // mount\n    await screen.findByText('data: 0')\n\n    act(() => {\n      // trigger revalidation\n      mutate(key)\n    })\n    await screen.findByText('data: 1')\n  })\n\n  it('should call function as data passing current cached value', async () => {\n    let mutate\n\n    const key = createKey()\n    function Page() {\n      mutate = useSWRConfig().mutate\n      const { data } = useSWR(key, null)\n      return <div>data: {data}</div>\n    }\n\n    // Prefill the cache with data\n    renderWithConfig(<Page />, {\n      provider: () => new Map([[key, { data: 'cached data' }]])\n    })\n\n    const callback = jest.fn()\n    await act(() => mutate(key, callback))\n    expect(callback).toHaveBeenCalledWith('cached data')\n  })\n\n  it('should call function with undefined if key not cached', async () => {\n    let cache, mutate\n\n    function App() {\n      const { cache: cache_, mutate: mutate_ } = useSWRConfig()\n      cache = cache_\n      mutate = mutate_\n      return null\n    }\n\n    renderWithConfig(<App />)\n\n    const increment = jest.fn(currentValue =>\n      currentValue == null ? undefined : currentValue + 1\n    )\n\n    const key = createKey()\n    await mutate(key, increment, false)\n\n    expect(increment).toHaveBeenCalledTimes(1)\n    expect(increment).toHaveBeenLastCalledWith(undefined)\n    expect(increment).toHaveLastReturnedWith(undefined)\n\n    cache.set(key, { ...cache.get(key), data: 42 })\n\n    await mutate(key, increment, false)\n\n    expect(increment).toHaveBeenCalledTimes(2)\n    expect(increment).toHaveBeenLastCalledWith(42)\n    expect(increment).toHaveLastReturnedWith(43)\n  })\n\n  it('should return results of the mutation', async () => {\n    const key = createKey()\n    // returns the data if the promise resolved\n    expect(globalMutate(key, Promise.resolve('data'))).resolves.toBe('data')\n\n    // throw the error if the promise rejected\n    expect(\n      globalMutate(key, Promise.reject(new Error('error')))\n    ).rejects.toBeInstanceOf(Error)\n  })\n\n  it('globalMutate should return undefined if the key is serialized to \"\" ', async () => {\n    // returns the data if the promise resolved\n    expect(globalMutate(null, Promise.resolve('data'))).resolves.toBe(undefined)\n\n    // throw the error if the promise rejected\n    const e = new Error('error')\n    expect(\n      globalMutate(() => {\n        throw e\n      }, Promise.resolve('data'))\n    ).rejects.toEqual(e)\n  })\n\n  it('should get bound mutate from useSWR', async () => {\n    const key = createKey()\n    function Page() {\n      const { data, mutate: boundMutate } = useSWR(key, () => 'fetched')\n      return (\n        <div onClick={() => boundMutate('mutated', false)}>data: {data}</div>\n      )\n    }\n\n    renderWithConfig(<Page />)\n    // hydration\n    screen.getByText('data:')\n\n    // mount\n    await screen.findByText('data: fetched')\n\n    // call bound mutate\n    fireEvent.click(screen.getByText('data: fetched'))\n    // expect a new updated value (after a tick)\n    await screen.findByText('data: mutated')\n  })\n\n  it('should ignore in flight requests when mutating', async () => {\n    const key = createKey()\n    // set it to 1\n    act(() => {\n      globalMutate(key, 1)\n    })\n\n    function Section() {\n      const { data } = useSWR(key, () => createResponse(2, { delay: 150 }))\n      return <div>{data}</div>\n    }\n\n    renderWithGlobalCache(<Section />)\n    screen.getByText('1') // directly from cache\n    await act(() => sleep(100)) // still suspending\n    act(() => {\n      globalMutate(key, 3)\n    }) // set it to 3. this will drop the ongoing request\n\n    await screen.findByText('3')\n\n    await act(() => sleep(100))\n    screen.getByText('3')\n  })\n\n  it('should ignore in flight mutations when calling another async mutate', async () => {\n    let value = 'off',\n      mutate\n    const key = createKey()\n    function Page() {\n      mutate = useSWRConfig().mutate\n      const { data } = useSWR(key, () => createResponse(value, { delay: 100 }))\n\n      return <div>{data}</div>\n    }\n\n    renderWithConfig(<Page />)\n\n    await screen.findByText('off') // Initial state\n\n    act(() => {\n      mutate(key, 'on', false)\n    })\n    // Validate local state is now \"on\"\n    await screen.findByText('on')\n\n    // Simulate toggling \"on\"\n    await act(async () => {\n      expect(\n        mutate(\n          key,\n          () => {\n            value = 'on'\n            return createResponse('on', { delay: 100 })\n          },\n          false\n        )\n      ).resolves.toBe('on')\n    })\n\n    act(() => {\n      mutate(key, 'off', false)\n    })\n\n    // Validate local state is now \"off\"\n    await screen.findByText('off')\n\n    // Simulate toggling \"off\"\n    await act(async () => {\n      expect(\n        mutate(\n          key,\n          () => {\n            value = 'off'\n            return createResponse('off', { delay: 100 })\n          },\n          false\n        )\n      ).resolves.toBe('off')\n    })\n\n    // Wait for toggling \"on\" promise to resolve, but the \"on\" mutation is canceled\n    await act(() => sleep(50))\n    screen.getByText('off')\n\n    // Wait for toggling \"off\" promise to resolve\n    await act(() => sleep(100))\n    screen.getByText('off')\n  })\n\n  it('null is stringified when found inside an array', async () => {\n    let value = 0,\n      mutate\n\n    function Page() {\n      mutate = useSWRConfig().mutate\n      const { data } = useSWR([null], () => value++, {\n        dedupingInterval: 0\n      })\n      return <div>data: {data}</div>\n    }\n\n    renderWithConfig(<Page />)\n    // hydration\n    screen.getByText('data:')\n\n    // mount\n    await screen.findByText('data: 0')\n\n    act(() => {\n      // trigger revalidation\n      mutate([null])\n    })\n    await screen.findByText('data: 1')\n  })\n\n  it('should return promise from mutate without data', async () => {\n    let value = 0,\n      mutate\n    const key = createKey()\n    function Page() {\n      mutate = useSWRConfig().mutate\n      const { data } = useSWR(key, () => value++, {\n        dedupingInterval: 0\n      })\n      return <div>data: {data}</div>\n    }\n\n    renderWithConfig(<Page />)\n    screen.getByText('data:')\n\n    // mount\n    await screen.findByText('data: 0')\n\n    let promise\n    await act(() => {\n      promise = mutate(key)\n      return promise\n    })\n    expect(promise).toBeInstanceOf(Promise) // mutate returns a promise\n    expect(promise).resolves.toBe(1) // the return value should be the new cache\n    screen.getByText('data: 1')\n  })\n\n  it('should not update error in global cache when mutate failed with error', async () => {\n    const value = 0\n    const key = createKey()\n    const message = 'mutate-error'\n\n    let cache, mutate\n    function Page() {\n      const { cache: cache_, mutate: mutate_ } = useSWRConfig()\n      cache = cache_\n      mutate = mutate_\n      const { data, error } = useSWR(key, () => value)\n      return <div>{error ? error.message : `data: ${data}`}</div>\n    }\n\n    renderWithConfig(<Page />)\n\n    // Mount\n    await screen.findByText('data: 0')\n    await act(async () => {\n      // mutate error will be thrown, add try catch to avoid crashing\n      try {\n        await mutate(\n          key,\n          () => {\n            throw new Error(message)\n          },\n          false\n        )\n      } catch (e) {\n        // do nothing\n      }\n    })\n\n    screen.getByText('data: 0')\n    const [keyInfo] = serialize(key)\n    const cacheError = cache.get(keyInfo)?.error\n    expect(cacheError).toBeUndefined()\n  })\n\n  it('should update error in global cache when mutate succeeded', async () => {\n    const key = createKey()\n\n    let mutate\n    function Page() {\n      const {\n        data,\n        error,\n        mutate: mutate_\n      } = useSWR<string>(key, async () => {\n        throw new Error('error')\n      })\n      mutate = mutate_\n      return <div>{error ? error.message : `data: ${data}`}</div>\n    }\n\n    renderWithConfig(<Page />)\n\n    // Mount\n    await screen.findByText('error')\n    act(() => {\n      mutate(v => v, { revalidate: false })\n    })\n    await screen.findByText('data: undefined')\n  })\n\n  it('should keep the `mutate` function referential equal', async () => {\n    const refs = []\n\n    let mutate\n    const updatedKey = createKey()\n    function Section() {\n      mutate = useSWRConfig().mutate\n      const [key, setKey] = useState(null)\n      const { data, mutate: boundMutate } = useSWR(key, () => createResponse(1))\n\n      useEffect(() => {\n        const timeout = setTimeout(() => setKey(updatedKey), 50)\n        return () => clearTimeout(timeout)\n      }, [])\n\n      refs.push(boundMutate)\n      return <div>{data}</div>\n    }\n\n    renderWithConfig(<Section />)\n    await act(() => sleep(100))\n    act(() => {\n      mutate(updatedKey, 2)\n    })\n    await act(() => sleep(50))\n\n    // check all `setSize`s are referential equal.\n    for (const ref of refs) {\n      expect(ref).toEqual(refs[0])\n    }\n  })\n\n  // https://github.com/vercel/swr/pull/1003\n  it.skip('should not dedupe synchronous mutations', async () => {\n    const mutationRecivedValues = []\n    const renderRecivedValues = []\n\n    const key = createKey()\n    function Component() {\n      const { data, mutate: boundMutate } = useSWR(key, () => 0)\n\n      useEffect(() => {\n        setTimeout(() => {\n          // let's mutate twice, synchronously\n          boundMutate(v => {\n            mutationRecivedValues.push(v) // should be 0\n            return 1\n          }, false)\n          boundMutate(v => {\n            mutationRecivedValues.push(v) // should be 1\n            return 2\n          }, false)\n        }, 1)\n        // the mutate function is guaranteed to be the same reference\n        // eslint-disable-next-line react-hooks/exhaustive-deps\n      }, [])\n\n      renderRecivedValues.push(data) // should be 0 -> 2, never render 1 in between\n      return null\n    }\n\n    renderWithConfig(<Component />)\n\n    await executeWithoutBatching(() => sleep(50))\n    expect(mutationRecivedValues).toEqual([0, 1])\n    expect(renderRecivedValues).toEqual([undefined, 0, 1, 2])\n  })\n\n  it('async mutation case 1 (startAt <= MUTATION_TS[key])', async () => {\n    let result = 0\n    const key = createKey()\n    const fetcher = jest.fn(createResponse)\n    function Component() {\n      const { data, mutate: boundMutate } = useSWR(\n        key,\n        () =>\n          fetcher(0, {\n            delay: 300\n          }),\n        {\n          dedupingInterval: 200\n        }\n      )\n      return (\n        <div\n          onClick={() => {\n            boundMutate(async () => {\n              result += 1\n              return createResponse(result, {\n                delay: 100\n              })\n            }, false)\n          }}\n        >\n          {data !== undefined ? `data: ${data.toString()}` : 'loading'}\n        </div>\n      )\n    }\n\n    renderWithConfig(<Component />)\n    screen.getByText('loading')\n\n    await act(() => sleep(50))\n\n    fireEvent.click(screen.getByText('loading'))\n\n    await act(() => sleep(100))\n    // mutate success\n    await screen.findByText('data: 1')\n\n    await act(() => sleep(150))\n    // fetcher result should be ignored\n    expect(fetcher).toHaveBeenCalledTimes(1)\n    await screen.findByText('data: 1')\n  })\n\n  it('async mutation case 2 (startAt <= MUTATION_END_TS[key])', async () => {\n    let result = 0,\n      mutate\n    const fetcher = jest.fn(createResponse)\n    const updatedKey = createKey()\n    function Component() {\n      const [key, setKey] = useState(null)\n      mutate = useSWRConfig().mutate\n      const { data } = useSWR(\n        key,\n        () =>\n          fetcher(0, {\n            delay: 400\n          }),\n        {\n          dedupingInterval: 200\n        }\n      )\n      useEffect(() => {\n        mutate(\n          updatedKey,\n          async () => {\n            result += 1\n            return createResponse(result, {\n              delay: 200\n            })\n          },\n          false\n        )\n        setKey(updatedKey)\n      }, [])\n      return (\n        <div>{data !== undefined ? `data: ${data.toString()}` : 'loading'}</div>\n      )\n    }\n\n    renderWithConfig(<Component />)\n    screen.getByText('loading')\n\n    // mutate success\n    await act(() => sleep(200))\n    fireEvent.click(screen.getByText('data: 1'))\n\n    // fetcher result should be ignored\n    await act(() => sleep(200))\n    expect(fetcher).toHaveBeenCalledTimes(1)\n    await screen.findByText('data: 1')\n  })\n\n  it('async mutation case 3 (MUTATION_END_TS[key] === 0)', async () => {\n    let result = 0,\n      mutate\n    const fetcher = jest.fn(createResponse)\n    const updatedKey = createKey()\n    function Component() {\n      const [key, setKey] = useState(null)\n      mutate = useSWRConfig().mutate\n      const { data } = useSWR(\n        key,\n        () =>\n          fetcher(0, {\n            delay: 100\n          }),\n        {\n          dedupingInterval: 200\n        }\n      )\n      useEffect(() => {\n        setKey(updatedKey)\n        mutate(\n          updatedKey,\n          async () => {\n            result += 1\n            return createResponse(result, { delay: 200 })\n          },\n          false\n        )\n      }, [])\n      return (\n        <div>{data !== undefined ? `data: ${data.toString()}` : 'loading'}</div>\n      )\n    }\n\n    renderWithConfig(<Component />)\n    screen.getByText('loading')\n\n    // fetcher result should be ignored\n    await act(() => sleep(100))\n    expect(fetcher).toHaveBeenCalledTimes(1)\n    screen.getByText('loading')\n\n    // mutate success\n    await act(() => sleep(100))\n    await screen.findByText('data: 1')\n  })\n\n  it('isValidating should be false when no fetcher is provided', async () => {\n    const key = createKey()\n    function Page() {\n      const { isValidating } = useSWR(key)\n      return <p>{isValidating.toString()}</p>\n    }\n    renderWithConfig(<Page />)\n    screen.getByText('false')\n  })\n\n  it('isLoading and isValidating should be false when revalidate is set to false', async () => {\n    const key = createKey()\n    function Page() {\n      const { data, isLoading, isValidating, mutate } = useSWR(\n        key,\n        () => 'data',\n        {\n          fallbackData: 'fallback',\n          revalidateOnMount: false,\n          revalidateOnFocus: false\n        }\n      )\n      return (\n        <div>\n          <p>data: {data}</p>\n          <p>isLoading: {isLoading.toString()}</p>\n          <p>isValidating: {isValidating.toString()}</p>\n          <button onClick={() => mutate('fallback1', { revalidate: false })}>\n            mutate\n          </button>\n        </div>\n      )\n    }\n    renderWithConfig(<Page />)\n    screen.getByText('data: fallback')\n    screen.getByText('isLoading: false')\n    screen.getByText('isValidating: false')\n    fireEvent.click(screen.getByText('mutate'))\n    await screen.findByText('data: fallback1')\n    screen.getByText('isLoading: false')\n    screen.getByText('isValidating: false')\n  })\n\n  it('bound mutate should always use the latest key', async () => {\n    const key = createKey()\n    const fetcher = jest.fn(() => 'data')\n    function Page() {\n      const [ready, setReady] = useState(false)\n      const { mutate: boundMutate } = useSWR(ready ? key : null, fetcher)\n      return (\n        <div>\n          <button onClick={() => setReady(true)}>set ready</button>\n          <button onClick={() => boundMutate()}>mutate</button>\n        </div>\n      )\n    }\n    renderWithConfig(<Page />)\n    screen.getByText('set ready')\n\n    expect(fetcher).toHaveBeenCalledTimes(0)\n\n    // it should trigger the fetch\n    fireEvent.click(screen.getByText('set ready'))\n    await act(() => sleep(10))\n    expect(fetcher).toHaveBeenCalledTimes(1)\n\n    // it should trigger the fetch again\n    fireEvent.click(screen.getByText('mutate'))\n    await act(() => sleep(10))\n    expect(fetcher).toHaveBeenCalledTimes(2)\n  })\n\n  it('should reset isValidating after mutate', async () => {\n    const key = createKey()\n    function Data() {\n      const { data, isValidating } = useSWR(key, () =>\n        createResponse('data', { delay: 30 })\n      )\n      const { cache } = useSWRConfig()\n      const [keyInfo] = serialize(key)\n      const cacheIsValidating = cache.get(keyInfo)?.isValidating\n      return (\n        <>\n          <p>data:{data}</p>\n          <p>isValidating:{isValidating.toString()}</p>\n          <p>cache:validating:{cacheIsValidating.toString()}</p>\n        </>\n      )\n    }\n\n    function Page() {\n      const { mutate: boundMutate } = useSWR(key, () =>\n        createResponse('data', { delay: 30 })\n      )\n      const [visible, setVisible] = useState(false)\n\n      return (\n        <div>\n          <button onClick={() => boundMutate(() => 'data', false)}>\n            preload\n          </button>\n          <button onClick={() => setVisible(true)}>show</button>\n          {visible && <Data />}\n        </div>\n      )\n    }\n    renderWithConfig(<Page />)\n\n    fireEvent.click(screen.getByText('preload'))\n    await act(() => sleep(20))\n    fireEvent.click(screen.getByText('show'))\n    screen.getByText('data:data')\n    screen.getByText('isValidating:true')\n    await act(() => sleep(20))\n    screen.getByText('data:data')\n    screen.getByText('isValidating:false')\n  })\n\n  it('should be able to mutate data to undefined', async () => {\n    const key = createKey()\n    function Page() {\n      const { data, mutate } = useSWR(key, () => 'foo')\n      return (\n        <>\n          <div>data: {String(data)}</div>\n          <button onClick={() => mutate(undefined, false)}>mutate</button>\n        </>\n      )\n    }\n\n    renderWithConfig(<Page />)\n    await screen.findByText('data: foo')\n\n    fireEvent.click(screen.getByText('mutate'))\n    await screen.findByText('data: undefined')\n  })\n\n  it('should be able to mutate data to undefined asynchronously', async () => {\n    const key = createKey()\n    function Page() {\n      const { data, mutate } = useSWR(key, () => 'foo')\n      return (\n        <>\n          <div>data: {String(data)}</div>\n          <button\n            onClick={() =>\n              mutate(\n                () => new Promise(res => setTimeout(() => res(undefined), 10)),\n                false\n              )\n            }\n          >\n            mutate\n          </button>\n        </>\n      )\n    }\n\n    renderWithConfig(<Page />)\n    await screen.findByText('data: foo')\n\n    fireEvent.click(screen.getByText('mutate'))\n    await screen.findByText('data: undefined')\n  })\n\n  // https://github.com/vercel/swr/issues/482\n  it('should be able to deduplicate multiple mutate calls', async () => {\n    const key = createKey()\n    const loggedData = []\n\n    function Page() {\n      const { data, mutate } = useSWR(key, () => 'foo')\n\n      useEffect(() => {\n        async function startMutation() {\n          await sleep(10)\n          mutate('sync1', false)\n          mutate(createResponse('async1', { delay: 50 }), false)\n          await sleep(10)\n          mutate('sync2', false)\n          mutate(createResponse('async2', { delay: 50 }), false)\n          await sleep(10)\n          mutate('sync3', false)\n          mutate(createResponse('async3', { delay: 50 }), false)\n        }\n\n        startMutation()\n      }, [mutate])\n\n      loggedData.push(data)\n      return null\n    }\n\n    renderWithConfig(<Page />)\n    await executeWithoutBatching(() => sleep(200))\n\n    // Only \"async3\" is left and others were deduped.\n    expect(loggedData).toEqual([\n      undefined,\n      'foo',\n      'sync1',\n      'sync2',\n      'sync3',\n      'async3'\n    ])\n  })\n\n  it('should ignore in flight mutation error when calling another async mutate', async () => {\n    const key = createKey()\n    const errorMutate = () =>\n      new Promise<string>((_, reject) => {\n        setTimeout(() => reject('error'), 200)\n      })\n\n    const successMutate = () =>\n      new Promise<string>(resolve => {\n        setTimeout(() => resolve('success'), 100)\n      })\n    function Page() {\n      const { data, mutate: boundMutate } = useSWR(key, () =>\n        createResponse('data', { delay: 100 })\n      )\n      return (\n        <div>\n          <div>{data}</div>\n          <button\n            onClick={() => {\n              boundMutate(successMutate, false)\n            }}\n          >\n            success-mutate\n          </button>\n          <button\n            onClick={() => {\n              boundMutate(errorMutate, false).catch(() => {})\n            }}\n          >\n            error-mutate\n          </button>\n        </div>\n      )\n    }\n    renderWithConfig(<Page />)\n    await screen.findByText('data')\n\n    fireEvent.click(screen.getByText('error-mutate'))\n    await sleep(50)\n\n    fireEvent.click(screen.getByText('success-mutate'))\n    await screen.findByText('success')\n\n    await sleep(300)\n    await screen.findByText('success')\n  })\n\n  it('should not update the cache when `populateCache` is disabled', async () => {\n    const key = createKey()\n    function Page() {\n      const { data, mutate } = useSWR(key, () => 'foo')\n      return (\n        <>\n          <div>data: {String(data)}</div>\n          <button\n            onClick={() =>\n              mutate('bar', {\n                revalidate: false,\n                populateCache: false\n              })\n            }\n          >\n            mutate\n          </button>\n        </>\n      )\n    }\n\n    renderWithConfig(<Page />)\n    await screen.findByText('data: foo')\n\n    fireEvent.click(screen.getByText('mutate'))\n    await sleep(30)\n    await screen.findByText('data: foo')\n  })\n\n  it('should support optimistic updates via `optimisticData`', async () => {\n    const key = createKey()\n    const renderedData = []\n    let mutate\n\n    function Page() {\n      const { data, mutate: boundMutate } = useSWR(key, () =>\n        createResponse('foo', { delay: 20 })\n      )\n      mutate = boundMutate\n      renderedData.push(data)\n      return <div>data: {String(data)}</div>\n    }\n\n    renderWithConfig(<Page />)\n    await screen.findByText('data: foo')\n\n    await executeWithoutBatching(() =>\n      mutate(createResponse('baz', { delay: 20 }), {\n        optimisticData: 'bar'\n      })\n    )\n    await act(() => sleep(30))\n    expect(renderedData).toEqual([undefined, 'foo', 'bar', 'baz', 'foo'])\n  })\n\n  it('should support optimistic updates via function `optimisticData`', async () => {\n    const key = createKey()\n    const renderedData = []\n    let mutate\n\n    function Page() {\n      const { data, mutate: boundMutate } = useSWR(key, () =>\n        createResponse('foo', { delay: 20 })\n      )\n      mutate = boundMutate\n      renderedData.push(data)\n      return <div>data: {String(data)}</div>\n    }\n\n    renderWithConfig(<Page />)\n    await screen.findByText('data: foo')\n\n    await executeWithoutBatching(() =>\n      mutate(createResponse('baz', { delay: 20 }), {\n        optimisticData: data => 'function_' + data\n      })\n    )\n    await act(() => sleep(30))\n    expect(renderedData).toEqual([\n      undefined,\n      'foo',\n      'function_foo',\n      'baz',\n      'foo'\n    ])\n  })\n\n  it('should be able use mutate to manipulate data via function `optimisticData`', async () => {\n    const key = createKey()\n    const renderedData = []\n\n    function useOptimisticDataMutate(_key, data, fallback) {\n      const { mutate } = useSWRConfig()\n      return () => {\n        return mutate(_key, createResponse(data, { delay: 20 }), {\n          optimisticData() {\n            return fallback\n          }\n        })\n      }\n    }\n\n    function Page() {\n      const mutateWithOptData = useOptimisticDataMutate(key, 'final', 'loading')\n      const { data } = useSWR(key)\n      renderedData.push(data)\n      return (\n        <div>\n          <button onClick={() => mutateWithOptData()}>mutate</button>\n          <div>data: {String(data)}</div>\n        </div>\n      )\n    }\n\n    renderWithConfig(<Page />)\n    fireEvent.click(screen.getByText('mutate'))\n    await act(() => sleep(30))\n\n    expect(renderedData).toEqual([undefined, 'loading', 'final'])\n  })\n\n  it('should be able to use functional optimistic data config and use second param `displayedData` to keep UI consistent in slow networks', async () => {\n    const key1 = createKey()\n    const key2 = createKey()\n    let data1 = 0\n    let data2 = 0\n\n    function useOptimisticData1Mutate() {\n      const { mutate } = useSWRConfig()\n      return () => {\n        return mutate(key1, () => createResponse(data1++, { delay: 1000 }), {\n          optimisticData(currentData) {\n            return currentData + 1 // optimistic update current data\n          }\n        })\n      }\n    }\n\n    function useOptimisticData2Mutate() {\n      const { mutate } = useSWRConfig()\n      return () => {\n        return mutate(key2, () => createResponse(data2++, { delay: 1000 }), {\n          optimisticData(_, displayedData) {\n            return displayedData + 1 // optimistic update displayed data\n          }\n        })\n      }\n    }\n\n    function Page() {\n      const mutateWithOptimisticallyUpdatedCurrentData =\n        useOptimisticData1Mutate()\n      const mutateWithOptimisticallyUpdatedDisplayedData =\n        useOptimisticData2Mutate()\n      const { data: renderedData1 } = useSWR<number>(key1, () =>\n        createResponse(data1, { delay: 1000 })\n      )\n      const { data: renderedData2 } = useSWR<number>(key2, () =>\n        createResponse(data2, { delay: 1000 })\n      )\n\n      return (\n        <div>\n          <button onClick={mutateWithOptimisticallyUpdatedCurrentData}>\n            incrementCurrent\n          </button>\n          <button onClick={mutateWithOptimisticallyUpdatedDisplayedData}>\n            incrementDisplayed\n          </button>\n          <div>\n            data: <span data-testid=\"data1\">{renderedData1}</span>\n          </div>\n          <div>\n            data: <span data-testid=\"data2\">{renderedData2}</span>\n          </div>\n        </div>\n      )\n    }\n\n    renderWithConfig(<Page />)\n    await act(() => sleep(1000)) // Wait for initial data to load\n    fireEvent.click(screen.getByText('incrementCurrent'))\n    fireEvent.click(screen.getByText('incrementDisplayed'))\n    fireEvent.click(screen.getByText('incrementCurrent'))\n    fireEvent.click(screen.getByText('incrementDisplayed'))\n    const renderedData1 = parseInt(\n      (await screen.findByTestId('data1')).innerHTML,\n      10\n    )\n    const renderedData2 = Number((await screen.findByTestId('data2')).innerHTML)\n    await act(() => sleep(2000)) // Wait for revalidation roundtrip\n    const renderedRevalidatedData1 = Number(\n      (await screen.findByTestId('data1')).innerHTML\n    )\n    const renderedRevalidatedData2 = Number(\n      (await screen.findByTestId('data2')).innerHTML\n    )\n    expect(data1).toEqual(2)\n    expect(renderedData1).toEqual(1)\n    expect(renderedRevalidatedData1).toEqual(2)\n    expect(data2).toEqual(2)\n    expect(renderedData2).toEqual(2)\n    expect(renderedRevalidatedData2).toEqual(2)\n  })\n\n  it('should prevent race conditions with optimistic UI', async () => {\n    const key = createKey()\n    const renderedData = []\n    let mutate\n\n    function Page() {\n      const { data, mutate: boundMutate } = useSWR(key, () => Math.random(), {\n        refreshInterval: 10,\n        dedupingInterval: 0\n      })\n      mutate = boundMutate\n      renderedData.push(data)\n      return <div>data: {String(data)}</div>\n    }\n\n    renderWithConfig(<Page />)\n\n    await act(() => sleep(20))\n    await executeWithoutBatching(() =>\n      mutate(createResponse('end', { delay: 50 }), {\n        optimisticData: 'start'\n      })\n    )\n    await act(() => sleep(20))\n\n    // There can never be any changes during a mutation — it should be atomic.\n    expect(renderedData.indexOf('end') - renderedData.indexOf('start')).toEqual(\n      1\n    )\n  })\n\n  it('should rollback optimistic updates when mutation fails', async () => {\n    const key = createKey()\n    const renderedData = []\n    let mutate\n    let cnt = 0\n\n    function Page() {\n      const { data, mutate: boundMutate } = useSWR(key, () =>\n        createResponse(cnt++, { delay: 20 })\n      )\n      mutate = boundMutate\n      if (\n        !renderedData.length ||\n        renderedData[renderedData.length - 1] !== data\n      ) {\n        renderedData.push(data)\n      }\n      return <div>data: {String(data)}</div>\n    }\n\n    renderWithConfig(<Page />)\n    await screen.findByText('data: 0')\n\n    try {\n      await executeWithoutBatching(() =>\n        mutate(createResponse(new Error('baz'), { delay: 20 }), {\n          optimisticData: 'bar'\n        })\n      )\n    } catch (e) {\n      expect(e.message).toEqual('baz')\n    }\n\n    await sleep(30)\n    expect(renderedData).toEqual([undefined, 0, 'bar', 0, 1])\n  })\n\n  it('should not revert to optimistic data when rolling back', async () => {\n    const key = createKey()\n    const renderedData = []\n    let mutate\n    let previousValue\n    let previousValue2\n\n    function Page() {\n      const { data, mutate: boundMutate } = useSWR(key, () =>\n        createResponse(0, { delay: 20 })\n      )\n      mutate = boundMutate\n\n      if (\n        !renderedData.length ||\n        renderedData[renderedData.length - 1] !== data\n      ) {\n        renderedData.push(data)\n      }\n\n      return <div>data: {String(data)}</div>\n    }\n\n    renderWithConfig(<Page />)\n    await screen.findByText('data: 0')\n\n    await executeWithoutBatching(async () => {\n      const p1 = mutate(createResponse(new Error(), { delay: 20 }), {\n        optimisticData: 1\n      })\n      await sleep(10)\n      const p2 = mutate(\n        v => {\n          previousValue = v\n          return createResponse(new Error(), { delay: 20 })\n        },\n        {\n          optimisticData: v => {\n            previousValue2 = v\n            return 2\n          }\n        }\n      )\n      return Promise.all([p1, p2])\n    }).catch(_e => {})\n\n    await sleep(30)\n\n    // It should revert to `0` instead of `1` at the end.\n    expect(renderedData).toEqual([undefined, 0, 1, 2, 0])\n\n    // It should receive the original displayed data instead of the currently displayed data.\n    expect(previousValue).toBe(0)\n    expect(previousValue2).toBe(0)\n  })\n\n  it('should rollback to the original value after multiple mutations', async () => {\n    const key = createKey()\n    const renderedData = []\n    let mutate\n    let serverData = 'foo'\n\n    function Page() {\n      const { data, mutate: boundMutate } = useSWR(key, () =>\n        createResponse(serverData, { delay: 20 })\n      )\n      mutate = boundMutate\n      if (\n        !renderedData.length ||\n        renderedData[renderedData.length - 1] !== data\n      ) {\n        renderedData.push(data)\n      }\n      return <div>data: {String(data)}</div>\n    }\n\n    // data == \"foo\"\n    renderWithConfig(<Page />)\n    await screen.findByText('data: foo')\n\n    // data == \"bar\"\n    await executeWithoutBatching(async () => {\n      await mutate(\n        createResponse('bar', { delay: 20 }).then(r => (serverData = r)),\n        {\n          optimisticData: 'bar',\n          populateCache: false\n        }\n      )\n    })\n    await sleep(30)\n    try {\n      // data == \"baz\", then reverted back to \"bar\"\n      await executeWithoutBatching(() =>\n        mutate(createResponse(new Error(), { delay: 20 }), {\n          optimisticData: 'baz',\n          revalidate: false\n        })\n      )\n    } catch (_) {\n      // Ignore\n    }\n\n    await sleep(30)\n    expect(renderedData).toEqual([undefined, 'foo', 'bar', 'baz', 'bar'])\n  })\n\n  it('should rollback to the original value after multiple mutations (2)', async () => {\n    const key = createKey()\n    const renderedData = []\n    let mutate\n    let serverData = 'foo'\n\n    function Page() {\n      const { data, mutate: boundMutate } = useSWR(key, () =>\n        createResponse(serverData, { delay: 20 })\n      )\n      mutate = boundMutate\n      if (\n        !renderedData.length ||\n        renderedData[renderedData.length - 1] !== data\n      ) {\n        renderedData.push(data)\n      }\n      return <div>data: {String(data)}</div>\n    }\n\n    // data == \"foo\"\n    renderWithConfig(<Page />)\n    await screen.findByText('data: foo')\n\n    // Here m1 and m2 overlap and m1 will be discarded.\n    await executeWithoutBatching(async () => {\n      const m1 = mutate(\n        createResponse('bar', { delay: 30 }).then(r => (serverData = r)),\n        {\n          optimisticData: 'bar',\n          populateCache: false\n        }\n      )\n\n      await sleep(10)\n\n      const m2 = mutate(\n        createResponse('baz', { delay: 30 }).then(r => (serverData = r))\n      )\n\n      await m1\n      await m2\n    })\n\n    try {\n      // data == \"qux\", then reverted back to \"baz\"\n      await executeWithoutBatching(() =>\n        mutate(createResponse(new Error(), { delay: 20 }), {\n          optimisticData: 'qux',\n          revalidate: false\n        })\n      )\n    } catch (_) {\n      // Ignore\n    }\n\n    // data: \"foo\" -> \"bar\" -> \"baz\" -> \"qux\" -> \"baz\"\n    //                 ^ optimistic      ^ error\n\n    await sleep(30)\n    expect(renderedData).toEqual([undefined, 'foo', 'bar', 'baz', 'qux', 'baz'])\n  })\n\n  it('should not rollback optimistic updates if `rollbackOnError` is disabled', async () => {\n    const key = createKey()\n    const renderedData = []\n    let mutate\n    let cnt = 0\n\n    function Page() {\n      const { data, mutate: boundMutate } = useSWR(key, () =>\n        createResponse(cnt++, { delay: 20 })\n      )\n      mutate = boundMutate\n      if (\n        !renderedData.length ||\n        renderedData[renderedData.length - 1] !== data\n      ) {\n        renderedData.push(data)\n      }\n      return <div>data: {String(data)}</div>\n    }\n\n    renderWithConfig(<Page />)\n    await screen.findByText('data: 0')\n\n    try {\n      await executeWithoutBatching(() =>\n        mutate(createResponse(new Error('baz-1'), { delay: 20 }), {\n          optimisticData: 'bar-1',\n          rollbackOnError: false\n        })\n      )\n    } catch (e) {\n      expect(e.message).toEqual('baz-1')\n    }\n\n    await sleep(30)\n    expect(renderedData).toEqual([undefined, 0, 'bar-1', 1])\n\n    let rollbackErrorMessage\n    try {\n      await executeWithoutBatching(() =>\n        mutate(createResponse(new Error('baz-2'), { delay: 20 }), {\n          optimisticData: 'bar-2',\n          rollbackOnError: error => {\n            rollbackErrorMessage = error.message\n            return false\n          }\n        })\n      )\n    } catch (e) {\n      expect(e.message).toEqual('baz-2')\n    }\n\n    await sleep(30)\n    expect(renderedData).toEqual([undefined, 0, 'bar-1', 1, 'bar-2', 2])\n    expect(rollbackErrorMessage).toEqual('baz-2')\n  })\n\n  it('should support transforming the result with `populateCache` before writing back', async () => {\n    const key = createKey()\n    function Page() {\n      const { data, mutate } = useSWR(key, () => 'foo')\n      return (\n        <>\n          <div>data: {String(data)}</div>\n          <button\n            onClick={() =>\n              mutate('bar', {\n                revalidate: false,\n                populateCache: v => '!' + v\n              })\n            }\n          >\n            mutate\n          </button>\n        </>\n      )\n    }\n\n    renderWithConfig(<Page />)\n    await screen.findByText('data: foo')\n\n    fireEvent.click(screen.getByText('mutate'))\n    await sleep(30)\n    await screen.findByText('data: !bar')\n  })\n\n  it('should support transforming the result with `populateCache` for async data with optimistic data', async () => {\n    const key = createKey()\n    const renderedData = []\n\n    let mutatePage\n\n    function Page() {\n      const { data, mutate } = useSWR(key, () => 'foo')\n      mutatePage = () =>\n        mutate(new Promise(res => setTimeout(() => res('baz'), 20)), {\n          optimisticData: () => 'bar',\n          revalidate: false,\n          populateCache: v => '!' + v\n        })\n\n      renderedData.push(data)\n      return null\n    }\n\n    renderWithConfig(<Page />)\n\n    await act(() => sleep(10))\n    await executeWithoutBatching(() => mutatePage())\n    await sleep(30)\n\n    expect(renderedData).toEqual([undefined, 'foo', 'bar', '!baz'])\n  })\n\n  it('should pass the original data snapshot to `populateCache` as the second parameter', async () => {\n    const key = createKey()\n    const renderedData = []\n\n    let serverData = ['Apple', 'Banana']\n\n    let appendData\n\n    const sendRequest = (newItem: string) => {\n      return new Promise<string>(res =>\n        setTimeout(() => {\n          // The server capitalizes the new item.\n          const modifiedData =\n            newItem.charAt(0).toUpperCase() + newItem.slice(1)\n          serverData = [...serverData, modifiedData]\n          res(modifiedData)\n        }, 20)\n      )\n    }\n\n    function Page() {\n      const { mutate } = useSWRConfig()\n      const { data } = useSWR(key, () => serverData)\n\n      appendData = () => {\n        return mutate<string[], string>(key, sendRequest('cherry'), {\n          optimisticData: [...data, 'cherry (optimistic)'],\n          populateCache: (result, currentData) => [\n            ...currentData,\n            result + ' (res)'\n          ],\n          revalidate: true\n        })\n      }\n\n      renderedData.push(data)\n      return null\n    }\n\n    renderWithConfig(<Page />)\n    await executeWithoutBatching(async () => {\n      await sleep(10)\n      await appendData()\n      await sleep(30)\n    })\n\n    expect(renderedData).toEqual([\n      undefined, // fetching\n      ['Apple', 'Banana'], // initial data\n      ['Apple', 'Banana', 'cherry (optimistic)'], // optimistic data\n      ['Apple', 'Banana', 'Cherry (res)'], // appended server response\n      ['Apple', 'Banana', 'Cherry'] // revalidated data\n    ])\n  })\n\n  it('should support key filter as first argument', async () => {\n    const key = createKey()\n    const mutationAllResults = []\n    const mutationOneResults = []\n\n    function Page() {\n      const { data: data1 } = useSWR(key + 'first', v => v)\n      const { data: data2 } = useSWR(key + 'second', v => v)\n      const { mutate } = useSWRConfig()\n      return (\n        <div>\n          <span\n            data-testid=\"mutator-filter-all\"\n            onClick={async () => {\n              const res = await mutate(\n                k => typeof k === 'string' && k.startsWith(key),\n                data => {\n                  return 'value-' + data.replace(key, '')\n                },\n                false\n              )\n              mutationAllResults.push(...res)\n            }}\n          />\n          <span\n            data-testid=\"mutator-filter-one\"\n            onClick={async () => {\n              const res = await mutate(\n                k => typeof k === 'string' && k.includes('first'),\n                () => 'value-first-g0',\n                false\n              )\n              mutationOneResults.push(...res)\n            }}\n          />\n          <p>first:{data1}</p>\n          <p>second:{data2}</p>\n        </div>\n      )\n    }\n    renderWithConfig(<Page />)\n\n    screen.getByText('first:')\n    screen.getByText('second:')\n\n    await nextTick()\n\n    // filter and mutate `first` and `second`\n    fireEvent.click(screen.getByTestId('mutator-filter-all'))\n    await nextTick()\n\n    await screen.findByText('first:value-first')\n    await screen.findByText('second:value-second')\n\n    expect(mutationAllResults).toEqual(['value-first', 'value-second'])\n\n    // only filter and mutate `first`\n    fireEvent.click(screen.getByTestId('mutator-filter-one'))\n    await nextTick()\n\n    await screen.findByText('first:value-first-g0')\n    await screen.findByText('second:value-second')\n\n    expect(mutationOneResults).toEqual(['value-first-g0'])\n  })\n\n  it('should remove all key value pairs when clear cache through key filter', async () => {\n    const key = createKey()\n    const mutationOneResults = []\n\n    function Page() {\n      const { data: data1 } = useSWR(key + 'first')\n      const { data: data2 } = useSWR(key + 'second')\n      const { mutate } = useSWRConfig()\n      return (\n        <div>\n          <span\n            data-testid=\"mutator-filter-all\"\n            onClick={async () => {\n              const promises = ['first', 'second'].map(async name => {\n                await mutate(key + name, `value-${name}`, false)\n              })\n              await Promise.all(promises)\n            }}\n          />\n          <span\n            data-testid=\"clear-all\"\n            onClick={async () => {\n              const res = await mutate(() => true, undefined, false)\n              mutationOneResults.push(...res)\n            }}\n          />\n          <p>first:{data1}</p>\n          <p>second:{data2}</p>\n        </div>\n      )\n    }\n    renderWithConfig(<Page />)\n\n    // add and mutate `first` and `second`\n    fireEvent.click(screen.getByTestId('mutator-filter-all'))\n    await nextTick()\n\n    await screen.findByText('first:value-first')\n    await screen.findByText('second:value-second')\n\n    // reset all keys to undefined\n    fireEvent.click(screen.getByTestId('clear-all'))\n    await nextTick()\n\n    await screen.findByText('first:')\n    await screen.findByText('second:')\n\n    expect(mutationOneResults).toEqual([undefined])\n  })\n\n  it('should pass the original key to the key filter', async () => {\n    const key = createKey()\n    const keys = []\n\n    function Page() {\n      useSWR([key, 'first'])\n      useSWR([key, 'second'])\n      useSWR(key)\n      const { mutate } = useSWRConfig()\n      return (\n        <span\n          data-testid=\"mutator-filter-all\"\n          onClick={() => {\n            mutate(\n              k => {\n                keys.push(k)\n                return false\n              },\n              undefined,\n              false\n            )\n          }}\n        />\n      )\n    }\n    renderWithConfig(<Page />)\n\n    // add and mutate `first` and `second`\n    fireEvent.click(screen.getByTestId('mutator-filter-all'))\n    await nextTick()\n\n    expect(keys).toEqual([[key, 'first'], [key, 'second'], key])\n  })\n\n  it('should skip speicla useSWRInfinite keys', async () => {\n    const key = createKey()\n    const keys = []\n\n    function Page() {\n      useSWR([key, 'first'])\n      useSWR([key, 'second'])\n      useSWR(key)\n      useSWRInfinite(\n        i => [key, 'inf', i],\n        k => k,\n        { initialSize: 2 }\n      )\n      const { mutate } = useSWRConfig()\n      return (\n        <span\n          data-testid=\"mutator-filter-all\"\n          onClick={() => {\n            mutate(\n              k => {\n                keys.push(k)\n                return false\n              },\n              undefined,\n              false\n            )\n          }}\n        />\n      )\n    }\n    renderWithConfig(<Page />)\n    await nextTick()\n\n    // add and mutate `first` and `second`\n    fireEvent.click(screen.getByTestId('mutator-filter-all'))\n\n    expect(keys).toEqual([\n      [key, 'first'],\n      [key, 'second'],\n      key,\n      [key, 'inf', 0],\n      [key, 'inf', 1]\n    ])\n  })\n  it('should support revalidate as a function', async () => {\n    let value = 0,\n      mutate\n    const key = createKey()\n    function Page() {\n      mutate = useSWRConfig().mutate\n      const { data } = useSWR(key, () => value++)\n      return <div>data: {data}</div>\n    }\n\n    renderWithConfig(<Page />)\n    screen.getByText('data:')\n\n    // mount\n    await screen.findByText('data: 0')\n\n    act(() => {\n      // value 0 -> 0\n      mutate(key, 100, { revalidate: () => false })\n    })\n    await screen.findByText('data: 100')\n\n    act(() => {\n      // value 0 -> 1\n      mutate(key, 200, { revalidate: () => true })\n    })\n    await screen.findByText('data: 200')\n    await screen.findByText('data: 1')\n  })\n\n  it('the function-style relivadate option receives the key and current data', async () => {\n    let value = 0,\n      mutate\n    const key = createKey()\n    function Page() {\n      mutate = useSWRConfig().mutate\n      const { data } = useSWR(key, () => value++)\n      return <div>data: {data}</div>\n    }\n\n    renderWithConfig(<Page />)\n    screen.getByText('data:')\n\n    // mount\n    await screen.findByText('data: 0')\n\n    act(() => {\n      // value 0 -> 0\n      mutate(key, 100, { revalidate: (d, k) => k === key && d === 200 }) // revalidate = false\n    })\n    await screen.findByText('data: 100')\n\n    act(() => {\n      // value 0 -> 1\n      mutate(key, 200, { revalidate: (d, k) => k === key && d === 200 }) // revalidate = true\n    })\n    await screen.findByText('data: 200')\n    await screen.findByText('data: 1')\n  })\n\n  it('the function-style relivadate option works with mutate filter', async () => {\n    const key1 = createKey()\n    const key2 = createKey()\n    const key3 = createKey()\n\n    let mockData = {\n      [key1]: 'page1',\n      [key2]: 'page2',\n      [key3]: 'page3'\n    }\n    function Page() {\n      const mutate = useSWRConfig().mutate\n      const { data: data1 } = useSWR(key1, () => mockData[key1])\n      const { data: data2 } = useSWR(key2, () => mockData[key2])\n      const { data: data3 } = useSWR(key3, () => mockData[key3])\n\n      return (\n        <>\n          <div>data1: {data1}</div>\n          <div>data2: {data2}</div>\n          <div>data3: {data3}</div>\n          <button\n            onClick={() => {\n              // key1 is filtered\n              mutate(k => k !== key1, 'updated', {\n                // only revalidate key3\n                revalidate: (d, k) => d === 'updated' && k === key3\n              })\n            }}\n          >\n            click\n          </button>\n        </>\n      )\n    }\n\n    renderWithConfig(<Page />)\n\n    // mount\n    await screen.findByText('data1: page1')\n    await screen.findByText('data2: page2')\n    await screen.findByText('data3: page3')\n\n    mockData = {\n      [key1]: '<page1>',\n      [key2]: '<page2>',\n      [key3]: '<page3>'\n    }\n\n    fireEvent.click(screen.getByText('click'))\n\n    await screen.findByText('data1: page1')\n    await screen.findByText('data2: updated')\n    await screen.findByText('data3: <page3>')\n  })\n})\n"
  },
  {
    "path": "test/use-swr-middlewares.test.tsx",
    "content": "import { screen } from '@testing-library/react'\nimport { useState, useEffect, useRef, act } from 'react'\nimport type { Middleware } from 'swr'\nimport useSWR, { SWRConfig } from 'swr'\nimport { withMiddleware } from 'swr/_internal'\n\nimport {\n  createResponse,\n  sleep,\n  createKey,\n  nextTick,\n  renderWithConfig\n} from './utils'\n\ndescribe('useSWR - middleware', () => {\n  it('should use middleware', async () => {\n    const key = createKey()\n    const mockConsoleLog = jest.fn(s => s)\n    const loggerMiddleware: Middleware = useSWRNext => (k, fn, config) => {\n      mockConsoleLog(k)\n      return useSWRNext(k, fn, config)\n    }\n    function Page() {\n      const { data } = useSWR(key, () => createResponse('data'), {\n        use: [loggerMiddleware]\n      })\n      return <div>hello, {data}</div>\n    }\n\n    renderWithConfig(<Page />)\n    screen.getByText('hello,')\n    await screen.findByText('hello, data')\n    expect(mockConsoleLog.mock.calls[0][0]).toBe(key)\n    // Initial render and data ready.\n    expect(mockConsoleLog.mock.calls.length).toBe(2)\n  })\n\n  it('should pass original keys to middleware', async () => {\n    const key = createKey()\n    const mockConsoleLog = jest.fn(s => s)\n    const loggerMiddleware: Middleware = useSWRNext => (k, fn, config) => {\n      mockConsoleLog(k)\n      return useSWRNext(k, fn, config)\n    }\n    function Page() {\n      const { data } = useSWR([key, 1, 2, 3], () => createResponse('data'), {\n        use: [loggerMiddleware]\n      })\n      return <div>hello, {data}</div>\n    }\n\n    renderWithConfig(<Page />)\n    screen.getByText('hello,')\n    await screen.findByText('hello, data')\n    expect(mockConsoleLog.mock.calls[0][0]).toEqual([key, 1, 2, 3])\n    // Initial render and data ready.\n    expect(mockConsoleLog.mock.calls.length).toBe(2)\n  })\n\n  it('should pass null fetcher to middleware', () => {\n    const key = createKey()\n    const mockConsoleLog = jest.fn(s => s)\n    const loggerMiddleware: Middleware = useSWRNext => (k, fn, config) => {\n      mockConsoleLog(fn)\n      return useSWRNext(k, fn, config)\n    }\n    function Page() {\n      const { data } = useSWR(key, null, {\n        use: [loggerMiddleware]\n      })\n      return <div>hello, {data}</div>\n    }\n\n    renderWithConfig(<Page />)\n    screen.getByText('hello,')\n    expect(mockConsoleLog.mock.calls[0][0]).toEqual(null)\n  })\n\n  it('should support `use` option in context', async () => {\n    const key = createKey()\n    const mockConsoleLog = jest.fn(s => s)\n    const loggerMiddleware: Middleware = useSWRNext => (k, fn, config) => {\n      mockConsoleLog(k)\n      return useSWRNext(k, fn, config)\n    }\n    function Page() {\n      const { data } = useSWR(key, () => createResponse('data'))\n      return <div>hello, {data}</div>\n    }\n\n    renderWithConfig(<Page />, { use: [loggerMiddleware] })\n    screen.getByText('hello,')\n    await screen.findByText('hello, data')\n    expect(mockConsoleLog.mock.calls[0][0]).toBe(key)\n    expect(mockConsoleLog.mock.calls.length).toBe(2)\n  })\n\n  it('should support extending middleware via context and per-hook config', async () => {\n    const key = createKey()\n    const mockConsoleLog = jest.fn((_, s) => s)\n    const createLoggerMiddleware =\n      (id: number): Middleware =>\n      useSWRNext =>\n      (k, fn, config) => {\n        mockConsoleLog(id, k)\n        return useSWRNext(k, fn, config)\n      }\n    function Page() {\n      const { data } = useSWR(key, () => createResponse('data'), {\n        use: [createLoggerMiddleware(0)]\n      })\n      return <div>hello, {data}</div>\n    }\n\n    renderWithConfig(\n      <SWRConfig value={{ use: [createLoggerMiddleware(2)] }}>\n        <SWRConfig value={{ use: [createLoggerMiddleware(1)] }}>\n          <Page />\n        </SWRConfig>\n      </SWRConfig>\n    )\n    screen.getByText('hello,')\n    await screen.findByText('hello, data')\n    expect(mockConsoleLog.mock.calls.map(call => call[0])).toEqual([\n      2, 1, 0, 2, 1, 0\n    ])\n  })\n\n  it('should use the correct middleware order in `withMiddleware`', async () => {\n    const key = createKey()\n    const mockConsoleLog = jest.fn((_, s) => s)\n    const createLoggerMiddleware =\n      (id: number): Middleware =>\n      useSWRNext =>\n      (k, fn, config) => {\n        mockConsoleLog(id + '-enter', k)\n        const swr = useSWRNext(k, fn, config)\n        mockConsoleLog(id + '-exit', k)\n        return swr\n      }\n\n    const customSWRHook = withMiddleware(useSWR, useSWRNext => (...args) => {\n      mockConsoleLog('0-enter', args[0])\n      const swr = useSWRNext(...args)\n      mockConsoleLog('0-exit', args[0])\n      return swr\n    })\n\n    function Page() {\n      const { data } = customSWRHook(key, () => createResponse('data'), {\n        use: [createLoggerMiddleware(1)]\n      })\n      return <div>hello, {data}</div>\n    }\n\n    renderWithConfig(<Page />, { use: [createLoggerMiddleware(2)] })\n    screen.getByText('hello,')\n    await screen.findByText('hello, data')\n    expect(mockConsoleLog.mock.calls.map(call => call[0])).toEqual([\n      '2-enter',\n      '1-enter',\n      '0-enter',\n      '0-exit',\n      '1-exit',\n      '2-exit',\n      '2-enter',\n      '1-enter',\n      '0-enter',\n      '0-exit',\n      '1-exit',\n      '2-exit'\n    ])\n  })\n\n  it('should support react hooks inside middleware', async () => {\n    const key = createKey()\n    const lazyMiddleware: Middleware = useSWRNext => (k, fn, config) => {\n      const dataRef = useRef(undefined)\n      const res = useSWRNext(k, fn, config)\n      if (res.data) {\n        dataRef.current = res.data\n        return res\n      } else {\n        return { ...res, data: dataRef.current }\n      }\n    }\n    function Page() {\n      const [mounted, setMounted] = useState(false)\n      const { data } = useSWR(`${key}-${mounted ? '1' : '0'}`, k =>\n        createResponse(k, { delay: 100 })\n      )\n      useEffect(() => {\n        setTimeout(() => setMounted(true), 200)\n      }, [])\n      return <div>data:{data}</div>\n    }\n\n    renderWithConfig(<Page />, { use: [lazyMiddleware] })\n\n    screen.getByText('data:') // undefined, time=0\n    await act(() => sleep(150))\n    screen.getByText(`data:${key}-0`) // 0, time=150\n    await act(() => sleep(100))\n    screen.getByText(`data:${key}-0`) // still holding the previous value, even if the key has changed\n    await act(() => sleep(100))\n    screen.getByText(`data:${key}-1`) // 1, time=350\n  })\n\n  it('should pass modified keys to the next middleware and useSWR', async () => {\n    const key = createKey()\n    const createDecoratingKeyMiddleware =\n      (c: string): Middleware =>\n      useSWRNext =>\n      (k, fn, config) => {\n        return useSWRNext(`${c}${k}${c}`, fn, config)\n      }\n\n    function Page() {\n      const { data } = useSWR(key, k => createResponse(k), {\n        use: [\n          createDecoratingKeyMiddleware('!'),\n          createDecoratingKeyMiddleware('#')\n        ]\n      })\n      return <div>hello, {data}</div>\n    }\n\n    renderWithConfig(<Page />)\n    screen.getByText('hello,')\n    await screen.findByText(`hello, #!${key}!#`)\n  })\n\n  it('should send the non-serialized key to the middleware', async () => {\n    const key = createKey()\n    const logger = jest.fn()\n\n    const m: Middleware = useSWRNext => (k, fn, config) => {\n      logger(Array.isArray(k))\n      return useSWRNext(JSON.stringify(k), _k => fn(...JSON.parse(_k)), config)\n    }\n\n    function Page() {\n      const { data } = useSWR(\n        [key, { hello: 'world' }],\n        (_, o) => {\n          return o.hello\n        },\n        {\n          use: [m]\n        }\n      )\n      return <div>hello, {data || ''}</div>\n    }\n\n    renderWithConfig(<Page />)\n    screen.getByText('hello,')\n    await nextTick()\n\n    expect(logger).toHaveBeenCalledWith(true)\n    screen.getByText('hello, world')\n  })\n})\n"
  },
  {
    "path": "test/use-swr-node-env.test.tsx",
    "content": "/**\n * @jest-environment node\n */\n\n// Do not lint the return value destruction for `renderToString`\n/* eslint-disable testing-library/render-result-naming-convention */\n\nimport { renderToString } from 'react-dom/server'\nimport useSWR from 'swr'\nimport useSWRImmutable from 'swr/immutable'\nimport { IS_SERVER } from 'swr/_internal'\nimport { createKey } from './utils'\n\ndescribe('useSWR', () => {\n  it('env IS_SERVER is true in node env', () => {\n    expect(IS_SERVER).toBe(true)\n  })\n\n  it('should render fallback if provided on server side', async () => {\n    const key = createKey()\n    const useData = () => useSWR(key, k => k, { fallbackData: 'fallback' })\n\n    function Page() {\n      const { data } = useData()\n      return <p>{data}</p>\n    }\n\n    const html = renderToString(<Page />)\n    expect(html).toContain('fallback')\n  })\n\n  it('should not revalidate useSWRImmutable on server side', async () => {\n    const key = createKey()\n    const useData = () => useSWRImmutable(key, k => k)\n\n    function Page() {\n      const { data } = useData()\n      return <p>{data || 'empty'}</p>\n    }\n\n    const html = renderToString(<Page />)\n    expect(html).toContain('empty')\n  })\n})\n"
  },
  {
    "path": "test/use-swr-offline.test.tsx",
    "content": "import { screen } from '@testing-library/react'\nimport { act } from 'react'\nimport useSWR from 'swr'\nimport {\n  nextTick as waitForNextTick,\n  focusOn,\n  createKey,\n  renderWithConfig\n} from './utils'\n\nconst focusWindow = () => focusOn(window)\nconst dispatchWindowEvent = event =>\n  act(async () => {\n    window.dispatchEvent(new Event(event))\n  })\n\ndescribe('useSWR - offline', () => {\n  it('should not revalidate when offline', async () => {\n    let value = 0\n\n    const key = createKey()\n    function Page() {\n      const { data } = useSWR(key, () => value++, {\n        dedupingInterval: 0\n      })\n      return <div>data: {data}</div>\n    }\n\n    renderWithConfig(<Page />)\n    // hydration\n    screen.getByText('data:')\n    // mount\n    await screen.findByText('data: 0')\n\n    // simulate offline\n    await waitForNextTick()\n    await dispatchWindowEvent('offline')\n\n    // trigger focus revalidation\n    await focusWindow()\n\n    // should not be revalidated\n    screen.getByText('data: 0')\n  })\n\n  it('should revalidate immediately when becoming online', async () => {\n    let value = 0\n\n    const key = createKey()\n    function Page() {\n      const { data } = useSWR(key, () => value++, {\n        dedupingInterval: 0\n      })\n      return <div>data: {data}</div>\n    }\n\n    renderWithConfig(<Page />)\n    // hydration\n    screen.getByText('data:')\n    // mount\n    await screen.findByText('data: 0')\n\n    // simulate online\n    await waitForNextTick()\n    await dispatchWindowEvent('online')\n\n    // should be revalidated\n    await screen.findByText('data: 1')\n  })\n})\n"
  },
  {
    "path": "test/use-swr-preload.test.tsx",
    "content": "import { fireEvent, screen } from '@testing-library/react'\nimport { Suspense, useEffect, useState, Profiler, act } from 'react'\nimport useSWR, { preload, useSWRConfig } from 'swr'\nimport {\n  createKey,\n  createResponse,\n  itShouldSkipForReactCanary,\n  renderWithGlobalCache,\n  sleep\n} from './utils'\n\ndescribe('useSWR - preload', () => {\n  it('preload the fetcher function', async () => {\n    const key = createKey()\n\n    const fetcher = jest.fn(() => createResponse('foo'))\n\n    function Page() {\n      const { data } = useSWR(key, fetcher)\n      return <div>data:{data}</div>\n    }\n\n    preload(key, fetcher)\n    expect(fetcher).toHaveBeenCalledTimes(1)\n\n    renderWithGlobalCache(<Page />)\n    await screen.findByText('data:foo')\n    expect(fetcher).toHaveBeenCalledTimes(1)\n  })\n\n  it('should avoid preloading the resource multiple times', async () => {\n    const key = createKey()\n    const fetcher = jest.fn(() => createResponse('foo'))\n\n    function Page() {\n      const { data } = useSWR(key, fetcher)\n      return <div>data:{data}</div>\n    }\n\n    preload(key, fetcher)\n    preload(key, fetcher)\n    preload(key, fetcher)\n    expect(fetcher).toHaveBeenCalledTimes(1)\n\n    renderWithGlobalCache(<Page />)\n    await screen.findByText('data:foo')\n    expect(fetcher).toHaveBeenCalledTimes(1)\n  })\n\n  it('should be able to prealod resources in effects', async () => {\n    const key = createKey()\n    const fetcher = jest.fn(() => createResponse('foo'))\n\n    function Comp() {\n      const { data } = useSWR(key, fetcher)\n      return <div>data:{data}</div>\n    }\n\n    function Page() {\n      const [show, setShow] = useState(false)\n      useEffect(() => {\n        preload(key, fetcher)\n      }, [])\n      return show ? (\n        <Comp />\n      ) : (\n        <button onClick={() => setShow(true)}>click</button>\n      )\n    }\n\n    renderWithGlobalCache(<Page />)\n    expect(fetcher).toHaveBeenCalledTimes(1)\n\n    fireEvent.click(screen.getByText('click'))\n\n    await screen.findByText('data:foo')\n    expect(fetcher).toHaveBeenCalledTimes(1)\n  })\n\n  itShouldSkipForReactCanary(\n    'preload the fetcher function with the suspense mode',\n    async () => {\n      const key = createKey()\n      const fetcher = jest.fn(() => createResponse('foo'))\n      const onRender = jest.fn()\n      function Page() {\n        const { data } = useSWR(key, fetcher, { suspense: true })\n        return <div>data:{data}</div>\n      }\n\n      preload(key, fetcher)\n      expect(fetcher).toHaveBeenCalledTimes(1)\n\n      renderWithGlobalCache(\n        <Suspense\n          fallback={\n            <Profiler id={key} onRender={onRender}>\n              loading\n            </Profiler>\n          }\n        >\n          <Page />\n        </Suspense>\n      )\n      await screen.findByText('data:foo')\n      expect(onRender).toHaveBeenCalledTimes(1)\n      expect(fetcher).toHaveBeenCalledTimes(1)\n    }\n  )\n\n  itShouldSkipForReactCanary(\n    'avoid suspense waterfall by prefetching the resources',\n    async () => {\n      const key1 = createKey()\n      const key2 = createKey()\n\n      const response1 = createResponse('foo', { delay: 50 })\n      const response2 = createResponse('bar', { delay: 50 })\n\n      const fetcher1 = () => response1\n      const fetcher2 = () => response2\n\n      function Page() {\n        const { data: data1 } = useSWR(key1, fetcher1, { suspense: true })\n        const { data: data2 } = useSWR(key2, fetcher2, { suspense: true })\n\n        return (\n          <div>\n            data:{data1}:{data2}\n          </div>\n        )\n      }\n\n      preload(key1, fetcher1)\n      preload(key2, fetcher2)\n\n      renderWithGlobalCache(\n        <Suspense fallback=\"loading\">\n          <Page />\n        </Suspense>\n      )\n      screen.getByText('loading')\n      // Should avoid waterfall(50ms + 50ms)\n      await act(() => sleep(80))\n      screen.getByText('data:foo:bar')\n    }\n  )\n\n  it('reset the preload result when the preload function gets an error', async () => {\n    const key = createKey()\n    let count = 0\n\n    const fetcher = () => {\n      ++count\n      const res = count === 1 ? new Error('err') : 'foo'\n      return createResponse(res)\n    }\n\n    let mutate\n    function Page() {\n      mutate = useSWRConfig().mutate\n      const { data, error } = useSWR<any>(key, fetcher)\n      if (error) {\n        return <div>error:{error.message}</div>\n      }\n      return <div>data:{data}</div>\n    }\n\n    try {\n      // error\n      await preload(key, fetcher)\n    } catch (e) {\n      // noop\n    }\n\n    renderWithGlobalCache(<Page />)\n    screen.getByText('data:')\n\n    // use the preloaded result\n    await screen.findByText('error:err')\n    expect(count).toBe(1)\n\n    // revalidate\n    await act(() => mutate(key))\n    // should not use the preload data\n    await screen.findByText('data:foo')\n  })\n\n  it('dedupe requests during preloading', async () => {\n    const key = createKey()\n\n    const fetcher = jest.fn(() =>\n      createResponse('foo', {\n        delay: 50\n      })\n    )\n    const onRender = jest.fn()\n\n    function Page() {\n      const { data } = useSWR(key, fetcher, { dedupingInterval: 0 })\n      return (\n        <Profiler id={key} onRender={onRender}>\n          data:{data}\n        </Profiler>\n      )\n    }\n\n    preload(key, fetcher)\n    expect(fetcher).toHaveBeenCalledTimes(1)\n\n    const { rerender } = renderWithGlobalCache(<Page />)\n    expect(onRender).toHaveBeenCalledTimes(1)\n    // rerender when the preloading is in-flight, and the deduping interval is over\n    await act(() => sleep(10))\n    rerender(<Page />)\n    expect(onRender).toHaveBeenCalledTimes(2)\n\n    await screen.findByText('data:foo')\n    expect(fetcher).toHaveBeenCalledTimes(1)\n    expect(onRender).toHaveBeenCalledTimes(3)\n  })\n\n  it('should pass serialize key to fetcher', async () => {\n    const key = createKey()\n    let calledWith: string\n\n    const fetcher = (args: string) => {\n      calledWith = args\n    }\n\n    preload(() => key, fetcher)\n    expect(calledWith).toBe(key)\n  })\n})\n"
  },
  {
    "path": "test/use-swr-promise.test.tsx",
    "content": "import { screen } from '@testing-library/react'\nimport useSWR, { SWRConfig } from 'swr'\nimport {\n  createKey,\n  createResponse,\n  itShouldSkipForReactCanary,\n  renderWithConfig,\n  sleep\n} from './utils'\nimport { Suspense, act } from 'react'\nimport { ErrorBoundary } from 'react-error-boundary'\n\ndescribe('useSWR - promise', () => {\n  itShouldSkipForReactCanary(\n    'should allow passing promises as fallback',\n    async () => {\n      const key = createKey()\n\n      const firstRender = [false, undefined] as [boolean, string | undefined]\n      function Page() {\n        const { data } = useSWR(key, () => {\n          return createResponse('new data', { delay: 100 })\n        })\n        if (!firstRender[0]) {\n          firstRender[0] = true\n          firstRender[1] = data\n        }\n        return <div>data:{data}</div>\n      }\n\n      const fetchData = createResponse('initial data', { delay: 100 })\n\n      renderWithConfig(\n        <SWRConfig\n          value={{\n            fallback: {\n              [key]: fetchData\n            }\n          }}\n        >\n          <Page />\n        </SWRConfig>\n      )\n\n      await screen.findByText('data:initial data')\n      await act(() => sleep(100)) // wait 100ms until the request inside finishes\n      await screen.findByText('data:new data')\n\n      expect(firstRender[1]).toEqual('initial data')\n    }\n  )\n\n  itShouldSkipForReactCanary(\n    'should allow passing promises as fallbackData',\n    async () => {\n      const key = createKey()\n\n      const fetchData = createResponse('initial data', { delay: 100 })\n      const firstRender = [false, undefined] as [boolean, string | undefined]\n\n      function Page() {\n        const { data } = useSWR(\n          key,\n          () => {\n            return createResponse('new data', { delay: 100 })\n          },\n          {\n            fallbackData: fetchData\n          }\n        )\n        if (!firstRender[0]) {\n          firstRender[0] = true\n          firstRender[1] = data\n        }\n        return <div>data:{data}</div>\n      }\n\n      renderWithConfig(<Page />)\n\n      await screen.findByText('data:initial data')\n      await act(() => sleep(100)) // wait 100ms until the request inside finishes\n      await screen.findByText('data:new data')\n\n      expect(firstRender[1]).toEqual('initial data')\n    }\n  )\n\n  itShouldSkipForReactCanary(\n    'should suspend when resolving the fallback promise',\n    async () => {\n      const key = createKey()\n\n      const firstRender = [false, undefined] as [boolean, string | undefined]\n      function Page() {\n        const { data } = useSWR(key, () => {\n          return createResponse('new data', { delay: 100 })\n        })\n        if (!firstRender[0]) {\n          firstRender[0] = true\n          firstRender[1] = data\n        }\n        return <div>data:{data}</div>\n      }\n\n      const fetchData = createResponse('initial data', { delay: 100 })\n\n      renderWithConfig(\n        <SWRConfig\n          value={{\n            fallback: {\n              [key]: fetchData\n            }\n          }}\n        >\n          <Suspense fallback={<div>loading</div>}>\n            <Page />\n          </Suspense>\n        </SWRConfig>\n      )\n\n      await screen.findByText('loading')\n      await screen.findByText('data:initial data')\n      await act(() => sleep(100)) // wait 100ms until the request inside finishes\n      await screen.findByText('data:new data')\n\n      expect(firstRender[1]).toEqual('initial data')\n    }\n  )\n\n  itShouldSkipForReactCanary(\n    'should handle errors with fallback promises',\n    async () => {\n      jest.spyOn(console, 'error').mockImplementation(() => {})\n\n      const key = createKey()\n\n      function Page() {\n        const { data } = useSWR(key)\n        return <div>data:{data}</div>\n      }\n\n      const fetchDataError = createResponse(new Error('error'), {\n        delay: 100\n      })\n\n      renderWithConfig(\n        <ErrorBoundary fallback={<div>error boundary</div>}>\n          <SWRConfig\n            value={{\n              fallback: {\n                [key]: fetchDataError\n              }\n            }}\n          >\n            <Suspense fallback={<div>loading</div>}>\n              <Page />\n            </Suspense>\n          </SWRConfig>\n        </ErrorBoundary>\n      )\n\n      await screen.findByText('loading')\n      await act(() => sleep(100)) // wait 100ms until the request inside throws\n      await screen.findByText('error boundary')\n    }\n  )\n\n  itShouldSkipForReactCanary(\n    'should handle same fallback promise that is already pending',\n    async () => {\n      const key = createKey()\n\n      function Comp() {\n        const { data } = useSWR(key)\n        return <>data:{data},</>\n      }\n\n      const fetchDataError = createResponse('value', {\n        delay: 100\n      })\n\n      renderWithConfig(\n        <SWRConfig\n          value={{\n            fallback: {\n              [key]: fetchDataError\n            }\n          }}\n        >\n          <Suspense fallback={<div>loading</div>}>\n            <Comp />\n            <Comp />\n          </Suspense>\n        </SWRConfig>\n      )\n\n      await screen.findByText('loading')\n      await act(() => sleep(100)) // wait 100ms until the request inside resolves\n      await screen.findByText('data:value,data:value,')\n    }\n  )\n})\n"
  },
  {
    "path": "test/use-swr-reconnect.test.tsx",
    "content": "import { screen, fireEvent, createEvent } from '@testing-library/react'\nimport useSWR from 'swr'\nimport {\n  nextTick as waitForNextTick,\n  renderWithConfig,\n  createKey,\n  mockVisibilityHidden\n} from './utils'\n\ndescribe('useSWR - reconnect', () => {\n  it('should revalidate on reconnect by default', async () => {\n    let value = 0\n    const key = createKey()\n    function Page() {\n      const { data } = useSWR(key, () => value++, {\n        dedupingInterval: 0\n      })\n      return <div>data: {data}</div>\n    }\n\n    renderWithConfig(<Page />)\n    // hydration\n    screen.getByText('data:')\n    // mount\n    await screen.findByText('data: 0')\n\n    await waitForNextTick()\n\n    // trigger reconnect\n    fireEvent(window, createEvent('offline', window))\n    fireEvent(window, createEvent('online', window))\n\n    await screen.findByText('data: 1')\n  })\n\n  it(\"shouldn't revalidate on reconnect when revalidateOnReconnect is false\", async () => {\n    let value = 0\n\n    const key = createKey()\n    function Page() {\n      const { data } = useSWR(key, () => value++, {\n        dedupingInterval: 0,\n        revalidateOnReconnect: false\n      })\n      return <div>data: {data}</div>\n    }\n\n    renderWithConfig(<Page />)\n    // hydration\n    screen.getByText('data:')\n\n    // mount\n    await screen.findByText('data: 0')\n\n    await waitForNextTick()\n\n    // trigger reconnect\n    fireEvent(window, createEvent('offline', window))\n    fireEvent(window, createEvent('online', window))\n\n    // should not be revalidated\n    screen.getByText('data: 0')\n  })\n\n  it(\"shouldn't revalidate on reconnect when isOnline is returning false\", async () => {\n    let value = 0\n\n    const key = createKey()\n    function Page() {\n      const { data } = useSWR(key, () => value++, {\n        dedupingInterval: 0,\n        isOnline: () => false\n      })\n      return <div>data: {data}</div>\n    }\n\n    renderWithConfig(<Page />)\n    // hydration\n    screen.getByText('data:')\n\n    // mount\n    await screen.findByText('data: 0')\n\n    await waitForNextTick()\n\n    // trigger reconnect\n    fireEvent(window, createEvent('offline', window))\n    fireEvent(window, createEvent('online', window))\n\n    // should not be revalidated\n    screen.getByText('data: 0')\n  })\n\n  it(\"shouldn't revalidate on reconnect if invisible\", async () => {\n    let value = 0\n\n    const key = createKey()\n    function Page() {\n      const { data } = useSWR(key, () => value++, {\n        dedupingInterval: 0,\n        isOnline: () => false\n      })\n      return <div>data: {data}</div>\n    }\n\n    renderWithConfig(<Page />)\n    // hydration\n    screen.getByText('data:')\n\n    // mount\n    await screen.findByText('data: 0')\n\n    await waitForNextTick()\n\n    const resetVisibility = mockVisibilityHidden()\n\n    // trigger reconnect\n    fireEvent(window, createEvent('offline', window))\n    fireEvent(window, createEvent('online', window))\n\n    // should not be revalidated\n    screen.getByText('data: 0')\n\n    resetVisibility()\n  })\n})\n"
  },
  {
    "path": "test/use-swr-refresh.test.tsx",
    "content": "import { fireEvent, screen } from '@testing-library/react'\nimport React, { useState, act } from 'react'\nimport useSWR, { SWRConfig, useSWRConfig } from 'swr'\nimport { createKey, renderWithConfig, sleep } from './utils'\n\n// This has to be an async function to wait for a microtask to flush updates\nconst advanceTimers = async (ms: number) => jest.advanceTimersByTime(ms) as any\n\n// This test heavily depends on setInterval/setTimeout timers, which makes tests slower and flaky.\n// So we use Jest's fake timers\ndescribe('useSWR - refresh', () => {\n  beforeAll(() => {\n    jest.useFakeTimers()\n  })\n  afterAll(() => {\n    jest.useRealTimers()\n  })\n  it('should rerender automatically on interval', async () => {\n    let count = 0\n\n    const key = createKey()\n    function Page() {\n      const { data } = useSWR(key, () => count++, {\n        refreshInterval: 200,\n        dedupingInterval: 100\n      })\n      return <div>count: {data}</div>\n    }\n\n    renderWithConfig(<Page />)\n\n    // hydration\n    screen.getByText('count:')\n\n    // mount\n    await screen.findByText('count: 0')\n\n    await act(() => advanceTimers(200)) // update\n    screen.getByText('count: 1')\n    await act(() => advanceTimers(50)) // no update\n    screen.getByText('count: 1')\n    await act(() => advanceTimers(150)) // update\n    screen.getByText('count: 2')\n  })\n\n  it('should dedupe requests combined with intervals', async () => {\n    let count = 0\n\n    const key = createKey()\n    function Page() {\n      const { data } = useSWR(key, () => count++, {\n        refreshInterval: 100,\n        dedupingInterval: 500\n      })\n      return <div>count: {data}</div>\n    }\n\n    renderWithConfig(<Page />)\n\n    // hydration\n    screen.getByText('count:')\n\n    // mount\n    await screen.findByText('count: 0')\n\n    await act(() => advanceTimers(100)) // no update (deduped)\n    screen.getByText('count: 0')\n    await act(() => advanceTimers(400)) // reach dudupingInterval\n    await act(() => advanceTimers(100)) // update\n    screen.getByText('count: 1')\n    await act(() => advanceTimers(100)) // no update (deduped)\n    screen.getByText('count: 1')\n    await act(() => advanceTimers(400)) // reach dudupingInterval\n    await act(() => advanceTimers(100)) // update\n    screen.getByText('count: 2')\n  })\n\n  it('should update data upon interval changes', async () => {\n    let count = 0\n    const key = createKey()\n    function Page() {\n      const [int, setInt] = React.useState(200)\n      const { data } = useSWR(key, () => count++, {\n        refreshInterval: int,\n        dedupingInterval: 50\n      })\n      return (\n        <div onClick={() => setInt(num => (num < 300 ? num + 50 : 0))}>\n          count: {data}\n        </div>\n      )\n    }\n\n    renderWithConfig(<Page />)\n    screen.getByText('count:')\n\n    // mount\n    await screen.findByText('count: 0')\n\n    await act(() => advanceTimers(200))\n    screen.getByText('count: 1')\n    await act(() => advanceTimers(50))\n    screen.getByText('count: 1')\n    await act(() => advanceTimers(200))\n    screen.getByText('count: 2')\n    fireEvent.click(screen.getByText('count: 2'))\n\n    await act(() => advanceTimers(50))\n\n    screen.getByText('count: 2')\n\n    await act(() => advanceTimers(200))\n\n    screen.getByText('count: 3')\n\n    await act(() => advanceTimers(250))\n    screen.getByText('count: 4')\n    fireEvent.click(screen.getByText('count: 4'))\n    await act(() => {\n      // it will clear the 250ms timer and set up a new 300ms timer\n      return advanceTimers(150)\n    })\n    screen.getByText('count: 4')\n    await act(() => advanceTimers(150))\n    screen.getByText('count: 5')\n    fireEvent.click(screen.getByText('count: 5'))\n    await act(() => {\n      // it will clear the 200ms timer and stop\n      return advanceTimers(50)\n    })\n    screen.getByText('count: 5')\n    await act(() => advanceTimers(300))\n    screen.getByText('count: 5')\n  })\n\n  it('should update data upon interval changes -- changes happened during revalidate', async () => {\n    let count = 0\n    const STOP_POLLING_THRESHOLD = 2\n    const key = createKey()\n    function Page() {\n      const [flag, setFlag] = useState(0)\n      const shouldPoll = flag < STOP_POLLING_THRESHOLD\n      const { data } = useSWR(key, () => count++, {\n        refreshInterval: shouldPoll ? 200 : 0,\n        dedupingInterval: 50,\n        onSuccess() {\n          setFlag(value => value + 1)\n        }\n      })\n      return (\n        <div onClick={() => setFlag(0)}>\n          count: {data} {flag}\n        </div>\n      )\n    }\n\n    renderWithConfig(<Page />)\n    screen.getByText('count: 0')\n\n    await screen.findByText('count: 0 1')\n\n    await act(() => advanceTimers(200))\n\n    screen.getByText('count: 1 2')\n\n    await act(() => advanceTimers(200))\n    screen.getByText('count: 1 2')\n\n    await act(() => advanceTimers(200))\n    screen.getByText('count: 1 2')\n\n    await act(() => advanceTimers(100))\n    screen.getByText('count: 1 2')\n\n    fireEvent.click(screen.getByText('count: 1 2'))\n\n    await act(() => {\n      // it will set up a new 200ms timer\n      return advanceTimers(50)\n    })\n\n    screen.getByText('count: 1 0')\n\n    await act(() => advanceTimers(150))\n\n    screen.getByText('count: 2 1')\n\n    await act(() => advanceTimers(200))\n\n    screen.getByText('count: 3 2')\n\n    await act(() => advanceTimers(200))\n\n    screen.getByText('count: 3 2')\n\n    await act(() => advanceTimers(200))\n\n    screen.getByText('count: 3 2')\n  })\n\n  it('should allow use custom compare method', async () => {\n    let count = 0\n    const key = createKey()\n    const fetcher = jest.fn(() => ({\n      timestamp: ++count,\n      version: '1.0'\n    }))\n    function Page() {\n      const { data, mutate: change } = useSWR(key, fetcher, {\n        compare: function compare(a, b) {\n          if (a === b) {\n            return true\n          }\n          if (!a || !b) {\n            return false\n          }\n          return a.version === b.version\n        }\n      })\n\n      if (!data) {\n        return <div>loading</div>\n      }\n      return <button onClick={() => change()}>{data.timestamp}</button>\n    }\n\n    let customCache\n\n    function App() {\n      return (\n        <SWRConfig\n          value={{\n            provider: () => {\n              return (customCache = new Map())\n            }\n          }}\n        >\n          <Page />\n        </SWRConfig>\n      )\n    }\n\n    renderWithConfig(<App />)\n\n    screen.getByText('loading')\n\n    await screen.findByText('1')\n    expect(fetcher).toHaveBeenCalledTimes(1)\n    expect(fetcher).toHaveReturnedWith({\n      timestamp: 1,\n      version: '1.0'\n    })\n\n    fireEvent.click(screen.getByText('1'))\n    await act(() => advanceTimers(1))\n    expect(fetcher).toHaveBeenCalledTimes(2)\n    expect(fetcher).toHaveReturnedWith({\n      timestamp: 2,\n      version: '1.0'\n    })\n\n    const cachedData = customCache.get(key)?.data\n    expect(cachedData.timestamp.toString()).toEqual('1')\n    screen.getByText('1')\n  })\n\n  it('custom compare should only be used for comparing data', async () => {\n    let count = 0\n    const key = createKey()\n    const compareParams = []\n    const fetcher = jest.fn(() => ({\n      timestamp: ++count,\n      version: '1.0'\n    }))\n    function Page() {\n      const config = useSWRConfig()\n      const { data, mutate: change } = useSWR(key, fetcher, {\n        compare: function (a, b) {\n          compareParams.push([a, b])\n          return config.compare(a, b)\n        },\n        dedupingInterval: 0\n      })\n\n      if (!data) {\n        return <div>loading</div>\n      }\n      return (\n        <>\n          <button onClick={() => change()}>change</button>\n          <div>{data.timestamp}</div>\n        </>\n      )\n    }\n    renderWithConfig(<Page />)\n    await screen.findByText('1')\n    expect(compareParams).toMatchInlineSnapshot(`\n      [\n        [\n          undefined,\n          undefined,\n        ],\n        [\n          undefined,\n          {\n            \"timestamp\": 1,\n            \"version\": \"1.0\",\n          },\n        ],\n        [\n          undefined,\n          {\n            \"timestamp\": 1,\n            \"version\": \"1.0\",\n          },\n        ],\n        [\n          undefined,\n          {\n            \"timestamp\": 1,\n            \"version\": \"1.0\",\n          },\n        ],\n        [\n          {\n            \"timestamp\": 1,\n            \"version\": \"1.0\",\n          },\n          undefined,\n        ],\n        [\n          {\n            \"timestamp\": 1,\n            \"version\": \"1.0\",\n          },\n          {\n            \"timestamp\": 1,\n            \"version\": \"1.0\",\n          },\n        ],\n        [\n          {\n            \"timestamp\": 1,\n            \"version\": \"1.0\",\n          },\n          {\n            \"timestamp\": 1,\n            \"version\": \"1.0\",\n          },\n        ],\n        [\n          {\n            \"timestamp\": 1,\n            \"version\": \"1.0\",\n          },\n          {\n            \"timestamp\": 1,\n            \"version\": \"1.0\",\n          },\n        ],\n      ]\n    `)\n\n    fireEvent.click(screen.getByText('change'))\n    await screen.findByText('2')\n    expect(compareParams).toMatchInlineSnapshot(`\n      [\n        [\n          undefined,\n          undefined,\n        ],\n        [\n          undefined,\n          {\n            \"timestamp\": 1,\n            \"version\": \"1.0\",\n          },\n        ],\n        [\n          undefined,\n          {\n            \"timestamp\": 1,\n            \"version\": \"1.0\",\n          },\n        ],\n        [\n          undefined,\n          {\n            \"timestamp\": 1,\n            \"version\": \"1.0\",\n          },\n        ],\n        [\n          {\n            \"timestamp\": 1,\n            \"version\": \"1.0\",\n          },\n          undefined,\n        ],\n        [\n          {\n            \"timestamp\": 1,\n            \"version\": \"1.0\",\n          },\n          {\n            \"timestamp\": 1,\n            \"version\": \"1.0\",\n          },\n        ],\n        [\n          {\n            \"timestamp\": 1,\n            \"version\": \"1.0\",\n          },\n          {\n            \"timestamp\": 1,\n            \"version\": \"1.0\",\n          },\n        ],\n        [\n          {\n            \"timestamp\": 1,\n            \"version\": \"1.0\",\n          },\n          {\n            \"timestamp\": 1,\n            \"version\": \"1.0\",\n          },\n        ],\n        [\n          {\n            \"timestamp\": 1,\n            \"version\": \"1.0\",\n          },\n          {\n            \"timestamp\": 1,\n            \"version\": \"1.0\",\n          },\n        ],\n        [\n          {\n            \"timestamp\": 1,\n            \"version\": \"1.0\",\n          },\n          {\n            \"timestamp\": 2,\n            \"version\": \"1.0\",\n          },\n        ],\n        [\n          {\n            \"timestamp\": 1,\n            \"version\": \"1.0\",\n          },\n          {\n            \"timestamp\": 2,\n            \"version\": \"1.0\",\n          },\n        ],\n        [\n          {\n            \"timestamp\": 2,\n            \"version\": \"1.0\",\n          },\n          {\n            \"timestamp\": 1,\n            \"version\": \"1.0\",\n          },\n        ],\n        [\n          {\n            \"timestamp\": 2,\n            \"version\": \"1.0\",\n          },\n          {\n            \"timestamp\": 2,\n            \"version\": \"1.0\",\n          },\n        ],\n        [\n          {\n            \"timestamp\": 2,\n            \"version\": \"1.0\",\n          },\n          {\n            \"timestamp\": 2,\n            \"version\": \"1.0\",\n          },\n        ],\n        [\n          {\n            \"timestamp\": 2,\n            \"version\": \"1.0\",\n          },\n          {\n            \"timestamp\": 2,\n            \"version\": \"1.0\",\n          },\n        ],\n      ]\n    `)\n  })\n\n  it('should not let the previous interval timer to set new timer if key changes too fast', async () => {\n    const key = createKey()\n    const fetcherWithToken = jest.fn(async token => {\n      await sleep(200)\n      return token\n    })\n    function Page() {\n      const [count, setCount] = useState(0)\n      const { data } = useSWR(`${key}-${count}`, fetcherWithToken, {\n        refreshInterval: 100,\n        dedupingInterval: 50\n      })\n      return (\n        <button\n          onClick={() => setCount(count + 1)}\n        >{`click me ${data}`}</button>\n      )\n    }\n\n    renderWithConfig(<Page />)\n\n    // initial revalidate\n    await act(() => advanceTimers(200))\n    expect(fetcherWithToken).toHaveBeenCalledTimes(1)\n\n    // first refresh\n    await act(() => advanceTimers(100))\n    expect(fetcherWithToken).toHaveBeenCalledTimes(2)\n    expect(fetcherWithToken).toHaveBeenLastCalledWith(`${key}-0`)\n    await act(() => advanceTimers(200))\n\n    // second refresh start\n    await act(() => advanceTimers(100))\n    expect(fetcherWithToken).toHaveBeenCalledTimes(3)\n    expect(fetcherWithToken).toHaveBeenLastCalledWith(`${key}-0`)\n    // change the key during revalidation\n    // The second refresh will not start a new timer\n    fireEvent.click(screen.getByText(`click me ${key}-0`))\n\n    // first, refresh with new key 1\n    await act(() => advanceTimers(100))\n    expect(fetcherWithToken).toHaveBeenCalledTimes(4)\n    expect(fetcherWithToken).toHaveBeenLastCalledWith(`${key}-1`)\n    await act(() => advanceTimers(200))\n\n    // second refresh with new key 1\n    await act(() => advanceTimers(100))\n    expect(fetcherWithToken).toHaveBeenCalledTimes(5)\n    expect(fetcherWithToken).toHaveBeenLastCalledWith(`${key}-1`)\n  })\n\n  it('should not call onSuccess from the previous interval if key has changed', async () => {\n    const fetcherWithToken = jest.fn(async token => {\n      await sleep(100)\n      return token\n    })\n    const onSuccess = jest.fn((data, key) => {\n      return `${data} ${key}`\n    })\n    const key = createKey()\n    function Page() {\n      const [count, setCount] = useState(0)\n      const { data } = useSWR(`${count.toString()}-${key}`, fetcherWithToken, {\n        refreshInterval: 50,\n        dedupingInterval: 25,\n        onSuccess\n      })\n      return (\n        <button\n          onClick={() => setCount(count + 1)}\n        >{`click me ${data}`}</button>\n      )\n    }\n\n    renderWithConfig(<Page />)\n\n    // initial revalidate\n    await act(() => advanceTimers(100))\n    expect(fetcherWithToken).toHaveBeenCalledTimes(1)\n    expect(onSuccess).toHaveBeenCalledTimes(1)\n    expect(onSuccess).toHaveLastReturnedWith(`0-${key} 0-${key}`)\n    // first refresh\n    await act(() => advanceTimers(50))\n    expect(fetcherWithToken).toHaveBeenCalledTimes(2)\n    expect(fetcherWithToken).toHaveBeenLastCalledWith(`0-${key}`)\n    await act(() => advanceTimers(100))\n    expect(onSuccess).toHaveBeenCalledTimes(2)\n    expect(onSuccess).toHaveLastReturnedWith(`0-${key} 0-${key}`)\n\n    // second refresh start\n    await act(() => advanceTimers(50))\n    expect(fetcherWithToken).toHaveBeenCalledTimes(3)\n    expect(fetcherWithToken).toHaveBeenLastCalledWith(`0-${key}`)\n    // change the key during revalidation\n    // The second refresh will not start a new timer\n    fireEvent.click(screen.getByText(`click me 0-${key}`))\n\n    // first, refresh with new key 1\n    await act(() => advanceTimers(50))\n    expect(fetcherWithToken).toHaveBeenCalledTimes(4)\n    expect(fetcherWithToken).toHaveBeenLastCalledWith(`1-${key}`)\n    await act(() => advanceTimers(100))\n    expect(onSuccess).toHaveBeenCalledTimes(3)\n    expect(onSuccess).toHaveLastReturnedWith(`1-${key} 1-${key}`)\n\n    // second refresh with new key 1\n    await act(() => advanceTimers(50))\n    expect(fetcherWithToken).toHaveBeenCalledTimes(5)\n    expect(fetcherWithToken).toHaveBeenLastCalledWith(`1-${key}`)\n  })\n\n  it('should allow using function as an interval', async () => {\n    let count = 0\n\n    const key = createKey()\n    function Page() {\n      const { data } = useSWR(key, () => count++, {\n        refreshInterval: () => 200,\n        dedupingInterval: 100\n      })\n      return <div>count: {data}</div>\n    }\n\n    renderWithConfig(<Page />)\n\n    // hydration\n    screen.getByText('count:')\n\n    // mount\n    await screen.findByText('count: 0')\n\n    await act(() => advanceTimers(200)) // update\n    screen.getByText('count: 1')\n    await act(() => advanceTimers(50)) // no update\n    screen.getByText('count: 1')\n    await act(() => advanceTimers(150)) // update\n    screen.getByText('count: 2')\n  })\n\n  it('should pass updated data to refreshInterval', async () => {\n    let count = 1\n\n    const key = createKey()\n    function Page() {\n      const { data } = useSWR(key, () => count++, {\n        refreshInterval: updatedCount => updatedCount * 1000,\n        dedupingInterval: 100\n      })\n      return <div>count: {data}</div>\n    }\n\n    renderWithConfig(<Page />)\n\n    // hydration\n    screen.getByText('count:')\n\n    // mount\n    await screen.findByText('count: 1')\n\n    await act(() => advanceTimers(1000)) // updated after 1s\n    screen.getByText('count: 2')\n    await act(() => advanceTimers(1000)) // no update\n    screen.getByText('count: 2')\n    await act(() => advanceTimers(1000)) // updated after 2s\n    screen.getByText('count: 3')\n    await act(() => advanceTimers(2000)) // no update\n    screen.getByText('count: 3')\n    await act(() => advanceTimers(1000)) // updated after 3s\n    screen.getByText('count: 4')\n  })\n\n  it('should pass updated data to refreshInterval, when refreshInterval is constant function', async () => {\n    let count = 1\n\n    // constant function\n    const refreshInterval = jest.fn(updatedCount => {\n      return updatedCount > 5 ? 0 : 1000\n    })\n\n    const key = createKey()\n    function Page() {\n      // constant function\n      // const refreshInterval = useCallback((updatedCount) => {\n      //   return updatedCount > 5 ? 0 : 1000\n      // }, [])\n\n      const { data } = useSWR(key, () => count++, {\n        refreshInterval,\n        dedupingInterval: 100\n      })\n      return <div>count: {data}</div>\n    }\n\n    renderWithConfig(<Page />)\n\n    // hydration\n    screen.getByText('count:')\n\n    // mount\n    await screen.findByText('count: 1')\n\n    await act(() => advanceTimers(1000))\n    screen.getByText('count: 2')\n    expect(refreshInterval).toHaveBeenLastCalledWith(2)\n    await act(() => advanceTimers(1000))\n    screen.getByText('count: 3')\n    expect(refreshInterval).toHaveBeenLastCalledWith(3)\n    await act(() => advanceTimers(1000))\n    screen.getByText('count: 4')\n    expect(refreshInterval).toHaveBeenLastCalledWith(4)\n  })\n\n  it('should disable refresh if function returns 0', async () => {\n    let count = 1\n\n    const key = createKey()\n    function Page() {\n      const { data } = useSWR(key, () => count++, {\n        refreshInterval: () => 0,\n        dedupingInterval: 100\n      })\n      return <div>count: {data}</div>\n    }\n\n    renderWithConfig(<Page />)\n\n    // hydration\n    screen.getByText('count:')\n\n    // mount\n    await screen.findByText('count: 1')\n\n    await act(() => advanceTimers(9999)) // no update\n    screen.getByText('count: 1')\n  })\n})\n"
  },
  {
    "path": "test/use-swr-remote-mutation.test.tsx",
    "content": "import { fireEvent, render, screen } from '@testing-library/react'\nimport React, { useState, act } from 'react'\nimport useSWR from 'swr'\nimport useSWRMutation from 'swr/mutation'\nimport { createKey, sleep, nextTick, createResponse } from './utils'\n\nconst waitForNextTick = () => act(() => sleep(1))\n\ndescribe('useSWR - remote mutation', () => {\n  it('should return data after triggering', async () => {\n    const key = createKey()\n\n    function Page() {\n      const { data, trigger } = useSWRMutation(key, () => 'data')\n      return <button onClick={() => trigger()}>{data || 'pending'}</button>\n    }\n\n    render(<Page />)\n\n    // mount\n    await screen.findByText('pending')\n\n    fireEvent.click(screen.getByText('pending'))\n    await waitForNextTick()\n\n    screen.getByText('data')\n  })\n\n  it('should return data from `trigger`', async () => {\n    const key = createKey()\n\n    function Page() {\n      const [data, setData] = React.useState(null)\n      const { trigger } = useSWRMutation(key, () => 'data')\n      return (\n        <button\n          onClick={async () => {\n            setData(await trigger())\n          }}\n        >\n          {data || 'pending'}\n        </button>\n      )\n    }\n\n    render(<Page />)\n\n    // mount\n    await screen.findByText('pending')\n\n    fireEvent.click(screen.getByText('pending'))\n    await waitForNextTick()\n\n    screen.getByText('data')\n  })\n\n  it('should trigger request with the correct argument signature', async () => {\n    const key = createKey()\n    const fetcher = jest.fn((_, __: { arg: string }) => 'data')\n\n    function Page() {\n      const { data, trigger } = useSWRMutation([key, 'arg0'], fetcher)\n      return (\n        <button onClick={() => trigger('arg1')}>{data || 'pending'}</button>\n      )\n    }\n\n    render(<Page />)\n\n    // mount\n    await screen.findByText('pending')\n    expect(fetcher).not.toHaveBeenCalled()\n\n    fireEvent.click(screen.getByText('pending'))\n    await waitForNextTick()\n\n    screen.getByText('data')\n\n    expect(fetcher).toHaveBeenCalled()\n    expect(fetcher.mock.calls.length).toBe(1)\n    expect(fetcher.mock.calls[0]).toEqual([[key, 'arg0'], { arg: 'arg1' }])\n  })\n\n  it('should call `onSuccess` event', async () => {\n    const key = createKey()\n    const onSuccess = jest.fn()\n\n    function Page() {\n      const { data, trigger } = useSWRMutation(key, () => 'data', {\n        onSuccess\n      })\n      return <button onClick={() => trigger()}>{data || 'pending'}</button>\n    }\n\n    render(<Page />)\n\n    // mount\n    await screen.findByText('pending')\n\n    fireEvent.click(screen.getByText('pending'))\n    await waitForNextTick()\n\n    screen.getByText('data')\n\n    expect(onSuccess).toHaveBeenCalled()\n  })\n\n  it('should support configuring `onSuccess` with trigger', async () => {\n    const key = createKey()\n    const onSuccess = jest.fn()\n\n    function Page() {\n      const { data, trigger } = useSWRMutation(key, () => 'data')\n      return (\n        <button onClick={() => trigger(null, { onSuccess })}>\n          {data || 'pending'}\n        </button>\n      )\n    }\n\n    render(<Page />)\n\n    // mount\n    await screen.findByText('pending')\n\n    fireEvent.click(screen.getByText('pending'))\n    await waitForNextTick()\n\n    screen.getByText('data')\n\n    expect(onSuccess).toHaveBeenCalled()\n  })\n\n  it('should call `onError` event and throw', async () => {\n    const key = createKey()\n    const onError = jest.fn()\n    const onInplaceError = jest.fn()\n\n    function Page() {\n      const { data, error, trigger } = useSWRMutation(\n        key,\n        async () => {\n          await sleep(10)\n          throw new Error('error!')\n        },\n        {\n          onError\n        }\n      )\n      return (\n        <button onClick={() => trigger().catch(onInplaceError)}>\n          {data || (error ? error.message : 'pending')}\n        </button>\n      )\n    }\n\n    render(<Page />)\n\n    // mount\n    await screen.findByText('pending')\n    fireEvent.click(screen.getByText('pending'))\n\n    await screen.findByText('error!')\n    expect(onError).toHaveBeenCalled()\n    expect(onInplaceError).toHaveBeenCalled()\n  })\n\n  it('should call `onError` event and skip throwing the error when `throwOnError` is disabled', async () => {\n    const key = createKey()\n    const onError = jest.fn()\n    const onInplaceError = jest.fn()\n\n    function Page() {\n      const { data, error, trigger } = useSWRMutation(\n        key,\n        async () => {\n          await sleep(10)\n          throw new Error('error!')\n        },\n        {\n          onError,\n          throwOnError: false\n        }\n      )\n      return (\n        <button onClick={() => trigger().catch(onInplaceError)}>\n          {data || (error ? error.message : 'pending')}\n        </button>\n      )\n    }\n\n    render(<Page />)\n\n    // mount\n    await screen.findByText('pending')\n    fireEvent.click(screen.getByText('pending'))\n\n    await screen.findByText('error!')\n    expect(onError).toHaveBeenCalled()\n    expect(onInplaceError).not.toHaveBeenCalled()\n  })\n\n  it('should return `isMutating` state correctly', async () => {\n    const key = createKey()\n\n    function Page() {\n      const { data, trigger, isMutating } = useSWRMutation(key, async () => {\n        await sleep(10)\n        return 'data'\n      })\n      return (\n        <button onClick={trigger}>\n          state:{(isMutating ? 'pending' : data) || ''}\n        </button>\n      )\n    }\n\n    render(<Page />)\n\n    // mount\n    await screen.findByText('state:')\n    fireEvent.click(screen.getByText('state:'))\n\n    await screen.findByText('state:pending')\n    await screen.findByText('state:data')\n  })\n\n  it('should send `onError` and `onSuccess` events', async () => {\n    const key = createKey()\n    const onSuccess = jest.fn()\n    const onError = jest.fn()\n\n    let arg = false\n\n    function Page() {\n      const { data, error, trigger } = useSWRMutation(\n        key,\n        async (_, { arg: shouldReturnValue }: { arg: boolean }) => {\n          await sleep(10)\n          if (shouldReturnValue) return 'data'\n          throw new Error('error')\n        },\n        {\n          onSuccess,\n          onError\n        }\n      )\n      return (\n        <button onClick={() => trigger(arg).catch(() => {})}>\n          {data || (error ? error.message : 'pending')}\n        </button>\n      )\n    }\n\n    render(<Page />)\n\n    // mount\n    await screen.findByText('pending')\n    fireEvent.click(screen.getByText('pending'))\n\n    await screen.findByText('error')\n    expect(onError).toHaveBeenCalled()\n    expect(onSuccess).not.toHaveBeenCalled()\n\n    arg = true\n    fireEvent.click(screen.getByText('error'))\n    await screen.findByText('data')\n    expect(onSuccess).toHaveBeenCalled()\n  })\n\n  it('should not dedupe trigger requests', async () => {\n    const key = createKey()\n    const fn = jest.fn()\n\n    function Page() {\n      const { trigger } = useSWRMutation(key, async () => {\n        fn()\n        await sleep(10)\n        return 'data'\n      })\n      return <button onClick={trigger}>trigger</button>\n    }\n\n    render(<Page />)\n\n    // mount\n    await screen.findByText('trigger')\n    expect(fn).not.toHaveBeenCalled()\n\n    fireEvent.click(screen.getByText('trigger'))\n    expect(fn).toHaveBeenCalledTimes(1)\n\n    fireEvent.click(screen.getByText('trigger'))\n    fireEvent.click(screen.getByText('trigger'))\n    fireEvent.click(screen.getByText('trigger'))\n    expect(fn).toHaveBeenCalledTimes(4)\n  })\n\n  it('should share the cache with `useSWR` when `populateCache` is enabled', async () => {\n    const key = createKey()\n\n    function Page() {\n      const { data } = useSWR(key)\n      const { trigger } = useSWRMutation(key, async () => {\n        await sleep(10)\n        return 'data'\n      })\n      return (\n        <div>\n          <button onClick={() => trigger(undefined, { populateCache: true })}>\n            trigger\n          </button>\n          <div>data:{data || 'none'}</div>\n        </div>\n      )\n    }\n\n    render(<Page />)\n\n    // mount\n    await screen.findByText('data:none')\n\n    fireEvent.click(screen.getByText('trigger'))\n    await screen.findByText('data:data')\n  })\n\n  it('should not read the cache from `useSWR`', async () => {\n    const key = createKey()\n\n    function Page() {\n      useSWR(key, () => 'data')\n      const { data } = useSWRMutation(key, () => 'wrong!')\n      return <div>data:{data || 'none'}</div>\n    }\n\n    render(<Page />)\n\n    // mount\n    await screen.findByText('data:none')\n  })\n\n  it('should be able to populate the cache for `useSWR`', async () => {\n    const key = createKey()\n\n    function Page() {\n      const { data } = useSWR(key, () => 'data')\n      const { trigger } = useSWRMutation(\n        key,\n        (_, { arg }: { arg: string }) => arg\n      )\n      return (\n        <div onClick={() => trigger('updated!', { populateCache: true })}>\n          data:{data || 'none'}\n        </div>\n      )\n    }\n\n    render(<Page />)\n\n    await screen.findByText('data:none')\n\n    // mount\n    await screen.findByText('data:data')\n\n    // mutate\n    fireEvent.click(screen.getByText('data:data'))\n    await screen.findByText('data:updated!')\n  })\n\n  it('should be able to populate the cache with a transformer', async () => {\n    const key = createKey()\n\n    function Page() {\n      const { data } = useSWR(key, () => 'data')\n      const { trigger } = useSWRMutation(\n        key,\n        (_, { arg }: { arg: string }) => arg\n      )\n      return (\n        <div\n          onClick={() =>\n            trigger('updated!', {\n              populateCache: (v, current) => v + ':' + current\n            })\n          }\n        >\n          data:{data || 'none'}\n        </div>\n      )\n    }\n\n    render(<Page />)\n\n    await screen.findByText('data:none')\n\n    // mount\n    await screen.findByText('data:data')\n\n    // mutate\n    fireEvent.click(screen.getByText('data:data'))\n    await screen.findByText('data:updated!:data')\n  })\n\n  it('should not trigger request when mutating from shared hooks', async () => {\n    const key = createKey()\n    const fn = jest.fn(() => 'data')\n\n    function Page() {\n      useSWRMutation(key, fn)\n      const { mutate } = useSWR(key)\n      return (\n        <div>\n          <button onClick={() => mutate()}>mutate</button>\n        </div>\n      )\n    }\n\n    render(<Page />)\n\n    // mount\n    await screen.findByText('mutate')\n\n    fireEvent.click(screen.getByText('mutate'))\n\n    await act(() => sleep(50))\n    expect(fn).not.toHaveBeenCalled()\n  })\n\n  it('should not trigger request when key changes', async () => {\n    const key = createKey()\n    const fn = jest.fn(() => 'data')\n\n    function Page() {\n      const [k, setK] = React.useState(key)\n      useSWRMutation(k, fn)\n      return (\n        <div>\n          <button onClick={() => setK(key + '_new')}>update key</button>\n        </div>\n      )\n    }\n\n    render(<Page />)\n\n    // mount\n    await screen.findByText('update key')\n\n    fireEvent.click(screen.getByText('update key'))\n\n    await act(() => sleep(50))\n    expect(fn).not.toHaveBeenCalled()\n  })\n\n  it('should prevent race conditions with `useSWR`', async () => {\n    const key = createKey()\n    const logger = jest.fn()\n\n    function Page() {\n      const { data } = useSWR(key, async () => {\n        await sleep(10)\n        return 'foo'\n      })\n      const { trigger } = useSWRMutation(key, async () => {\n        await sleep(20)\n        return 'bar'\n      })\n\n      logger(data)\n\n      return (\n        <div>\n          <button\n            onClick={() =>\n              trigger(undefined, { revalidate: false, populateCache: true })\n            }\n          >\n            trigger\n          </button>\n          <div>data:{data || 'none'}</div>\n        </div>\n      )\n    }\n\n    render(<Page />)\n\n    // mount\n    await screen.findByText('data:none')\n\n    fireEvent.click(screen.getByText('trigger'))\n    await act(() => sleep(50))\n    await screen.findByText('data:bar')\n\n    // It should never render `foo`.\n    expect(logger).not.toHaveBeenCalledWith('foo')\n  })\n\n  it('should revalidate after mutating by default', async () => {\n    const key = createKey()\n    const logger = jest.fn()\n\n    function Page() {\n      const { data } = useSWR(\n        key,\n        async () => {\n          await sleep(10)\n          return 'foo'\n        },\n        { revalidateOnMount: false }\n      )\n      const { trigger } = useSWRMutation(key, async () => {\n        await sleep(20)\n        return 'bar'\n      })\n\n      logger(data)\n\n      return (\n        <div>\n          <button onClick={() => trigger(undefined)}>trigger</button>\n          <div>data:{data || 'none'}</div>\n        </div>\n      )\n    }\n\n    render(<Page />)\n\n    // mount\n    await screen.findByText('data:none')\n\n    fireEvent.click(screen.getByText('trigger'))\n    await act(() => sleep(50))\n\n    // It triggers revalidation\n    await screen.findByText('data:foo')\n\n    // It should never render `bar` since we never populate the cache.\n    expect(logger).not.toHaveBeenCalledWith('bar')\n  })\n\n  it('should revalidate the SWR request that starts during the mutation', async () => {\n    const key = createKey()\n\n    function Page() {\n      const [triggered, setTriggered] = React.useState(false)\n      const { data } = useSWR(\n        triggered ? key : null,\n        async () => {\n          await sleep(10)\n          return 'foo'\n        },\n        { revalidateOnMount: false }\n      )\n      const { trigger } = useSWRMutation(key, async () => {\n        await sleep(20)\n        return 'bar'\n      })\n\n      return (\n        <div>\n          <button\n            onClick={() => {\n              trigger(undefined)\n              setTriggered(true)\n            }}\n          >\n            trigger\n          </button>\n          <div>data:{data || 'none'}</div>\n        </div>\n      )\n    }\n\n    render(<Page />)\n\n    // mount\n    await screen.findByText('data:none')\n\n    fireEvent.click(screen.getByText('trigger'))\n    await act(() => sleep(50))\n\n    // The SWR request that starts during the mutation should be revalidated.\n    await screen.findByText('data:foo')\n  })\n\n  it('should revalidate after populating the cache', async () => {\n    const key = createKey()\n    const logger = jest.fn()\n\n    function Page() {\n      const { data } = useSWR(\n        key,\n        async () => {\n          await sleep(20)\n          return 'foo'\n        },\n        { revalidateOnMount: false }\n      )\n      const { trigger } = useSWRMutation(key, async () => {\n        await sleep(20)\n        return 'bar'\n      })\n\n      logger(data)\n\n      return (\n        <div>\n          <button onClick={() => trigger(undefined, { populateCache: true })}>\n            trigger\n          </button>\n          <div>data:{data || 'none'}</div>\n        </div>\n      )\n    }\n\n    render(<Page />)\n\n    // mount\n    await screen.findByText('data:none')\n\n    fireEvent.click(screen.getByText('trigger'))\n\n    // Cache is updated\n    await screen.findByText('data:bar')\n\n    // A revalidation is triggered\n    await screen.findByText('data:foo')\n  })\n\n  it('should be able to turn off auto revalidation', async () => {\n    const key = createKey()\n    const logger = jest.fn()\n\n    function Page() {\n      const { data } = useSWR(\n        key,\n        async () => {\n          await sleep(10)\n          return 'foo'\n        },\n        { revalidateOnMount: false }\n      )\n      const { trigger } = useSWRMutation(\n        key,\n        async () => {\n          await sleep(20)\n          return 'bar'\n        },\n        { revalidate: false, populateCache: true }\n      )\n\n      logger(data)\n\n      return (\n        <div>\n          <button onClick={() => trigger(undefined)}>trigger</button>\n          <div>data:{data || 'none'}</div>\n        </div>\n      )\n    }\n\n    render(<Page />)\n\n    // mount\n    await screen.findByText('data:none')\n\n    fireEvent.click(screen.getByText('trigger'))\n    await act(() => sleep(50))\n\n    // It should not trigger revalidation\n    await screen.findByText('data:bar')\n\n    // It should never render `foo`.\n    expect(logger).not.toHaveBeenCalledWith('foo')\n  })\n\n  it('should be able to configure auto revalidation from trigger', async () => {\n    const key = createKey()\n    const logger = jest.fn()\n\n    function Page() {\n      const { data } = useSWR(\n        key,\n        async () => {\n          await sleep(10)\n          return 'foo'\n        },\n        { revalidateOnMount: false }\n      )\n      const { trigger } = useSWRMutation(\n        key,\n        async () => {\n          await sleep(20)\n          return 'bar'\n        },\n        { populateCache: true }\n      )\n\n      logger(data)\n\n      return (\n        <div>\n          <button onClick={() => trigger(undefined, { revalidate: false })}>\n            trigger1\n          </button>\n          <button onClick={() => trigger(undefined, { revalidate: true })}>\n            trigger2\n          </button>\n          <div>data:{data || 'none'}</div>\n        </div>\n      )\n    }\n\n    render(<Page />)\n\n    // mount\n    await screen.findByText('data:none')\n\n    fireEvent.click(screen.getByText('trigger1'))\n    await act(() => sleep(50))\n\n    // It should not trigger revalidation\n    await screen.findByText('data:bar')\n\n    // It should never render `foo`.\n    expect(logger).not.toHaveBeenCalledWith('foo')\n\n    fireEvent.click(screen.getByText('trigger2'))\n    await act(() => sleep(50))\n\n    // It should trigger revalidation\n    await screen.findByText('data:foo')\n  })\n\n  it('should be able to reset the state', async () => {\n    const key = createKey()\n\n    function Page() {\n      const { data, trigger, reset } = useSWRMutation(key, async () => {\n        return 'data'\n      })\n\n      return (\n        <div>\n          <button onClick={trigger}>trigger</button>\n          <button onClick={reset}>reset</button>\n          <div>data:{data || 'none'}</div>\n        </div>\n      )\n    }\n\n    render(<Page />)\n\n    // mount\n    await screen.findByText('data:none')\n\n    fireEvent.click(screen.getByText('trigger'))\n\n    // Cache is updated\n    await screen.findByText('data:data')\n\n    // reset\n    fireEvent.click(screen.getByText('reset'))\n    await screen.findByText('data:none')\n  })\n\n  it('should prevent race condition if reset the state', async () => {\n    const key = createKey()\n    const onSuccess = jest.fn()\n\n    function Page() {\n      const { data, trigger, reset } = useSWRMutation(key, async () => {\n        await sleep(10)\n        return 'data'\n      })\n\n      return (\n        <div>\n          <button onClick={() => trigger(undefined, { onSuccess })}>\n            trigger\n          </button>\n          <button onClick={reset}>reset</button>\n          <div>data:{data || 'none'}</div>\n        </div>\n      )\n    }\n\n    render(<Page />)\n\n    // mount\n    await screen.findByText('data:none')\n\n    // start mutation\n    fireEvent.click(screen.getByText('trigger'))\n\n    // reset, before it ends\n    fireEvent.click(screen.getByText('reset'))\n\n    await act(() => sleep(30))\n    await screen.findByText('data:none')\n  })\n\n  it('should prevent race condition if triggered multiple times', async () => {\n    const key = createKey()\n    const logger = []\n\n    let id = 0\n    function Page() {\n      const { data, trigger } = useSWRMutation(key, async () => {\n        await sleep(10)\n        return id++\n      })\n\n      logger.push(data)\n\n      return <button onClick={trigger}>trigger</button>\n    }\n\n    render(<Page />)\n\n    // Mount\n    await screen.findByText('trigger')\n\n    // Start mutation multiple times, to break the previous one\n    fireEvent.click(screen.getByText('trigger')) // 0\n    await act(() => sleep(5))\n    fireEvent.click(screen.getByText('trigger')) // 1\n    await act(() => sleep(5))\n    fireEvent.click(screen.getByText('trigger')) // 2\n    await act(() => sleep(20))\n\n    // Shouldn't have intermediate states\n    expect(logger).toEqual([undefined, 2])\n  })\n\n  it('should error if no mutator is given', async () => {\n    const key = createKey()\n    const catchError = jest.fn()\n\n    function Page() {\n      const { trigger } = useSWRMutation(key, null)\n\n      return (\n        <div>\n          <button onClick={() => trigger().catch(catchError)}>trigger</button>\n        </div>\n      )\n    }\n\n    render(<Page />)\n\n    fireEvent.click(screen.getByText('trigger'))\n    await nextTick()\n    expect(catchError).toHaveBeenCalled()\n  })\n\n  it('should support optimistic updates', async () => {\n    const key = createKey()\n\n    function Page() {\n      const { data } = useSWR(key, async () => {\n        await sleep(10)\n        return ['foo']\n      })\n      const { trigger } = useSWRMutation(\n        key,\n        async (_, { arg }: { arg: string }) => {\n          await sleep(20)\n          return arg.toUpperCase()\n        }\n      )\n\n      return (\n        <div>\n          <button\n            onClick={() =>\n              trigger<typeof data>('bar', {\n                optimisticData: current => [...current, 'bar'],\n                populateCache: (added, current) => [...current, added],\n                revalidate: false\n              })\n            }\n          >\n            trigger\n          </button>\n          <div>data:{JSON.stringify(data)}</div>\n        </div>\n      )\n    }\n\n    render(<Page />)\n\n    // mount\n    await screen.findByText('data:[\"foo\"]')\n\n    // optimistic update\n    fireEvent.click(screen.getByText('trigger'))\n    await screen.findByText('data:[\"foo\",\"bar\"]')\n    await act(() => sleep(50))\n    await screen.findByText('data:[\"foo\",\"BAR\"]')\n\n    // 2nd check\n    fireEvent.click(screen.getByText('trigger'))\n    await screen.findByText('data:[\"foo\",\"BAR\",\"bar\"]')\n    await act(() => sleep(50))\n    await screen.findByText('data:[\"foo\",\"BAR\",\"BAR\"]')\n  })\n\n  it('should clear error after successful trigger', async () => {\n    const key = createKey()\n\n    let arg = false\n\n    function Page() {\n      const { error, trigger } = useSWRMutation(\n        key,\n        async (_, { arg: shouldReturnValue }: { arg: boolean }) => {\n          await sleep(10)\n          if (shouldReturnValue) return ['foo']\n          throw new Error('error')\n        }\n      )\n\n      return (\n        <div>\n          <button onClick={() => trigger(arg).catch(() => {})}>trigger</button>\n          <div>Error: {error ? error.message : 'none'}</div>\n        </div>\n      )\n    }\n\n    render(<Page />)\n\n    fireEvent.click(screen.getByText('trigger'))\n    await screen.findByText('Error: error')\n\n    arg = true\n\n    fireEvent.click(screen.getByText('trigger'))\n    await screen.findByText('Error: none')\n  })\n\n  it('should always use the latest fetcher', async () => {\n    const key = createKey()\n\n    function Page() {\n      const [count, setCount] = useState(0)\n      const { data, trigger } = useSWRMutation(key, () => count)\n\n      return (\n        <div>\n          <button\n            onClick={() => {\n              setCount(c => c + 1)\n            }}\n          >\n            ++\n          </button>\n          <button\n            onClick={() => {\n              trigger()\n            }}\n          >\n            trigger\n          </button>\n          <div>\n            data:{data},count:{count}\n          </div>\n        </div>\n      )\n    }\n\n    render(<Page />)\n\n    await screen.findByText('data:,count:0')\n    fireEvent.click(screen.getByText('trigger'))\n    await screen.findByText('data:0,count:0')\n\n    fireEvent.click(screen.getByText('++'))\n    await screen.findByText('data:0,count:1')\n\n    fireEvent.click(screen.getByText('trigger'))\n    await screen.findByText('data:1,count:1')\n  })\n\n  it('should always use the latest config', async () => {\n    const key = createKey()\n    const logs = []\n\n    function Page() {\n      const [count, setCount] = useState(0)\n      const { data, trigger } = useSWRMutation(key, async () => count, {\n        onSuccess() {\n          logs.push(count)\n        }\n      })\n\n      return (\n        <div>\n          <button\n            onClick={() => {\n              setCount(c => c + 1)\n            }}\n          >\n            ++\n          </button>\n          <button\n            onClick={() => {\n              trigger()\n            }}\n          >\n            trigger\n          </button>\n          <div>\n            data:{data},count:{count}\n          </div>\n        </div>\n      )\n    }\n\n    render(<Page />)\n\n    await screen.findByText('data:,count:0')\n    expect(logs).toEqual([])\n    fireEvent.click(screen.getByText('trigger'))\n    await screen.findByText('data:0,count:0')\n    expect(logs).toEqual([0])\n\n    fireEvent.click(screen.getByText('++'))\n    await screen.findByText('data:0,count:1')\n    expect(logs).toEqual([0])\n\n    fireEvent.click(screen.getByText('trigger'))\n    await screen.findByText('data:1,count:1')\n    expect(logs).toEqual([0, 1])\n  })\n\n  it('should support revalidate as a function', async () => {\n    const key = createKey()\n\n    let value = 0\n\n    function Page() {\n      const { data } = useSWR(key, () => createResponse(++value))\n      const { trigger } = useSWRMutation(key, () => {\n        value += 10\n        return createResponse(value)\n      })\n\n      return (\n        <div>\n          <button\n            onClick={() =>\n              trigger(undefined, {\n                revalidate: (d, k) => k === key && d < 30,\n                populateCache: true\n              })\n            }\n          >\n            trigger\n          </button>\n          <div>data:{data || 'none'}</div>\n        </div>\n      )\n    }\n\n    render(<Page />)\n\n    // mount\n    await screen.findByText('data:1')\n\n    fireEvent.click(screen.getByText('trigger'))\n    await screen.findByText('data:12')\n    fireEvent.click(screen.getByText('trigger'))\n    await screen.findByText('data:23')\n    fireEvent.click(screen.getByText('trigger'))\n    await screen.findByText('data:33')\n\n    // stop revalidation because value > 30\n    fireEvent.click(screen.getByText('trigger'))\n    await screen.findByText('data:43')\n    fireEvent.click(screen.getByText('trigger'))\n    await screen.findByText('data:53')\n  })\n\n  it('should error when triggering an empty key', async () => {\n    let error = null\n\n    function Page() {\n      const { trigger } = useSWRMutation(null, () => {})\n      return (\n        <button\n          onClick={async () => {\n            try {\n              await trigger()\n            } catch (err) {\n              error = err\n            }\n          }}\n        >\n          {'pending'}\n        </button>\n      )\n    }\n\n    render(<Page />)\n\n    // mount\n    await screen.findByText('pending')\n\n    fireEvent.click(screen.getByText('pending'))\n    await waitForNextTick()\n\n    expect(error.message).toBe('Can’t trigger the mutation: missing key.')\n  })\n\n  it('should call `onError` and `onRejected` but do not call `onSuccess` if value an error is cast to false', async () => {\n    const key = createKey()\n    const onSuccess = jest.fn()\n    const onError = jest.fn()\n    const onRejected = jest.fn()\n\n    const fetcher = () => {\n      return new Promise((_, reject) => reject(''))\n    }\n\n    function Page() {\n      const { trigger } = useSWRMutation(key, fetcher, { onError, onSuccess })\n\n      return (\n        <button onClick={() => trigger().catch(onRejected)}>trigger</button>\n      )\n    }\n\n    render(<Page />)\n\n    await screen.findByText('trigger')\n    fireEvent.click(screen.getByText('trigger'))\n\n    await nextTick()\n\n    expect(onSuccess).not.toHaveBeenCalled()\n    expect(onError).toHaveBeenCalled()\n    expect(onRejected).toHaveBeenCalled()\n  })\n})\n"
  },
  {
    "path": "test/use-swr-revalidate.test.tsx",
    "content": "import { fireEvent, screen } from '@testing-library/react'\nimport { useState, act } from 'react'\nimport useSWR, { useSWRConfig } from 'swr'\nimport {\n  createResponse,\n  sleep,\n  nextTick as waitForNextTick,\n  createKey,\n  renderWithConfig,\n  nextTick\n} from './utils'\n\ndescribe('useSWR - revalidate', () => {\n  it('should rerender after triggering revalidation', async () => {\n    let value = 0\n\n    const key = createKey()\n    function Page() {\n      const { data, mutate } = useSWR(key, () => value++)\n      return <button onClick={() => mutate()}>data: {data}</button>\n    }\n\n    renderWithConfig(<Page />)\n\n    // hydration\n    screen.getByText('data:')\n\n    // mount\n    await screen.findByText('data: 0')\n\n    fireEvent.click(screen.getByText('data: 0'))\n    await waitForNextTick()\n    screen.getByText('data: 1')\n  })\n\n  it('should revalidate all the hooks with the same key', async () => {\n    let value = 0\n\n    const key = createKey()\n    function Page() {\n      const { data: v1, mutate } = useSWR(key, () => value++)\n      const { data: v2 } = useSWR(key, () => value++)\n      return (\n        <button onClick={() => mutate()}>\n          {v1}, {v2}\n        </button>\n      )\n    }\n\n    renderWithConfig(<Page />)\n\n    // hydration\n    screen.getByText(',')\n\n    // mount\n    await screen.findByText('0, 0')\n\n    fireEvent.click(screen.getByText('0, 0'))\n\n    await waitForNextTick()\n    screen.getByText('1, 1')\n  })\n\n  it('should respect sequences of revalidation calls (cope with race condition)', async () => {\n    let faster = false\n\n    const key = createKey()\n    function Page() {\n      const { data, mutate } = useSWR(key, () =>\n        createResponse(faster ? 1 : 0, { delay: faster ? 50 : 100 })\n      )\n\n      return <button onClick={() => mutate()}>data: {data}</button>\n    }\n\n    renderWithConfig(<Page />)\n\n    // hydration\n    screen.getByText('data:')\n\n    // trigger the slower revalidation\n    faster = false\n    fireEvent.click(screen.getByText('data:'))\n\n    await waitForNextTick()\n    // trigger the faster revalidation\n    faster = true\n    fireEvent.click(screen.getByText('data:'))\n\n    await act(() => sleep(150))\n    screen.getByText('data: 1')\n  })\n\n  it('should keep isValidating be true when there are two concurrent requests', async () => {\n    const key = createKey()\n    function Page() {\n      const { isValidating, mutate } = useSWR(\n        key,\n        () => createResponse(null, { delay: 100 }),\n        { revalidateOnMount: false }\n      )\n\n      return (\n        <button onClick={() => mutate()}>\n          {isValidating ? 'true' : 'false'}\n        </button>\n      )\n    }\n\n    renderWithConfig(<Page />)\n    screen.getByText('false')\n\n    // trigger the first revalidation\n    fireEvent.click(screen.getByText('false'))\n    await act(() => sleep(50))\n    screen.getByText('true')\n\n    fireEvent.click(screen.getByText('true'))\n    await act(() => sleep(70))\n    // the first revalidation is over, the second revalidation is still in progress\n    screen.getByText('true')\n\n    await act(() => sleep(70))\n    screen.getByText('false')\n  })\n\n  it('should respect sequences of revalidation calls although in dedupingInterval', async () => {\n    let count = 0\n    const key = createKey()\n    function Page() {\n      const { data, mutate } = useSWR(\n        key,\n        () => {\n          const currCount = ++count\n          return createResponse(currCount, { delay: currCount === 1 ? 50 : 0 })\n        },\n        {\n          dedupingInterval: 30\n        }\n      )\n      return <div onClick={() => mutate()}>count: {data}</div>\n    }\n\n    renderWithConfig(<Page />)\n\n    await waitForNextTick()\n    fireEvent.click(screen.getByText('count:'))\n    await act(() => sleep(70))\n    screen.getByText('count: 2')\n  })\n\n  it('should set initial isValidating be false when config.isPaused returns true', async () => {\n    function Page() {\n      const { isValidating } = useSWR(\n        'set isValidating for config.isPaused',\n        () => '123',\n        { isPaused: () => true }\n      )\n\n      return <div>{isValidating ? 'true' : 'false'}</div>\n    }\n\n    renderWithConfig(<Page />)\n    screen.getByText('false')\n  })\n\n  it('should mark the key as invalidated and clear deduping with `mutate`, even if there is no mounted hook', async () => {\n    const key = createKey()\n    let cnt = 0\n\n    function Foo() {\n      const { data } = useSWR(key, () => 'data: ' + cnt++, {\n        dedupingInterval: 1000\n      })\n      return <>{data}</>\n    }\n\n    function Page() {\n      const [showFoo, setShowFoo] = useState(true)\n      const { mutate } = useSWRConfig()\n      return (\n        <>\n          {showFoo ? <Foo /> : null}\n          <button onClick={() => setShowFoo(!showFoo)}>toggle</button>\n          <button onClick={() => mutate(key)}>mutate</button>\n        </>\n      )\n    }\n\n    renderWithConfig(<Page />)\n    await nextTick()\n    screen.getByText('data: 0')\n    fireEvent.click(screen.getByText('toggle'))\n    await nextTick()\n    fireEvent.click(screen.getByText('mutate'))\n    await nextTick()\n    fireEvent.click(screen.getByText('toggle'))\n    await act(() => sleep(20))\n    screen.getByText('data: 1')\n  })\n})\n"
  },
  {
    "path": "test/use-swr-server.test.tsx",
    "content": "// This test case covers special environments such as React <= 17 and SSR.\n\nimport { screen, render } from '@testing-library/react'\nimport { Suspense } from 'react'\nimport { ErrorBoundary } from 'react-error-boundary'\n\n// https://github.com/jestjs/jest/issues/11471\njest.mock('react', () => jest.requireActual('react'))\n\nasync function withServer(runner: () => Promise<void>) {\n  await jest.isolateModulesAsync(async () => {\n    await runner()\n  })\n}\n\ndescribe('useSWR - SSR', () => {\n  describe('preload on server', () => {\n    beforeAll(() => {\n      // @ts-expect-error\n      global.window.Deno = '1'\n    })\n\n    afterAll(() => {\n      // @ts-expect-error\n      delete global.window.Deno\n    })\n\n    it('should be a no-op on the server', async () => {\n      await withServer(async () => {\n        const { preload } = await import('swr')\n\n        const fetcher = jest.fn(() => 'data')\n        const result = preload('test-key', fetcher)\n\n        // preload should return undefined on the server\n        expect(result).toBeUndefined()\n        // fetcher should not be called on the server\n        expect(fetcher).not.toHaveBeenCalled()\n      })\n    })\n  })\n\n  describe('IS_SERVER flag', () => {\n    beforeAll(() => {\n      // Store the original window object\n      // @ts-expect-error\n      global.window.Deno = '1'\n\n      // Mock window to undefined\n      // delete global.window;\n    })\n\n    afterAll(() => {\n      // Restore window back to its original value\n      // @ts-expect-error\n      delete global.window.Deno\n    })\n\n    it('should enable the IS_SERVER flag - suspense on server without fallback', async () => {\n      await withServer(async () => {\n        jest.spyOn(console, 'error').mockImplementation(() => {})\n        const useSWR = (await import('swr')).default\n\n        const key = Math.random().toString()\n\n        const Page = () => {\n          const { data } = useSWR(key, () => 'SWR', {\n            suspense: true\n          })\n          return <div>{data || 'empty'}</div>\n        }\n\n        render(\n          <ErrorBoundary\n            fallbackRender={({ error }) => {\n              console.error(error)\n              return <div>{error.message}</div>\n            }}\n          >\n            <Suspense>\n              <Page />\n            </Suspense>\n          </ErrorBoundary>\n        )\n\n        await screen.findByText(\n          'Fallback data is required when using Suspense in SSR.'\n        )\n      })\n    })\n  })\n\n  describe('strictServerPrefetchWarning', () => {\n    it('should show console warning on when strictServerPrefetchWarning is enabled', async () => {\n      await withServer(async () => {\n        const warnings: string[] = []\n\n        const warnSpy = jest.spyOn(console, 'warn').mockImplementation(msg => {\n          warnings.push(msg as string)\n        })\n\n        const { default: useSWR, SWRConfig } = await import('swr')\n\n        let resolve: (() => void) | null = null\n        const promise = new Promise<void>(r => {\n          resolve = r\n        })\n\n        const Page = () => {\n          useSWR('ssr:1', () => 'SWR')\n          useSWR('ssr:2', () => 'SWR')\n          useSWR('ssr:3', () => 'SWR', { strictServerPrefetchWarning: false })\n          useSWR('ssr:4', () => 'SWR', { fallbackData: 'SWR' })\n          useSWR('ssr:5', () => 'SWR')\n\n          resolve!()\n\n          return null\n        }\n\n        render(\n          <SWRConfig\n            value={{\n              strictServerPrefetchWarning: true,\n              fallback: {\n                'ssr:5': 'SWR'\n              }\n            }}\n          >\n            <Page />\n          </SWRConfig>,\n          {\n            hydrate: true\n          }\n        )\n\n        await promise\n\n        expect(warnings).toMatchInlineSnapshot(`\n          [\n            \"Missing pre-initiated data for serialized key \"ssr:1\" during server-side rendering. Data fetching should be initiated on the server and provided to SWR via fallback data. You can set \"strictServerPrefetchWarning: false\" to disable this warning.\",\n            \"Missing pre-initiated data for serialized key \"ssr:2\" during server-side rendering. Data fetching should be initiated on the server and provided to SWR via fallback data. You can set \"strictServerPrefetchWarning: false\" to disable this warning.\",\n          ]\n        `)\n\n        warnSpy.mockClear()\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "test/use-swr-streaming-ssr.test.tsx",
    "content": "import { Suspense, act } from 'react'\nimport useSWR from 'swr'\nimport {\n  createKey,\n  createResponse,\n  renderWithConfig,\n  hydrateWithConfig,\n  mockConsoleForHydrationErrors,\n  sleep\n} from './utils'\n\ndescribe('useSWR - streaming', () => {\n  afterEach(() => {\n    jest.clearAllMocks()\n    jest.restoreAllMocks()\n  })\n\n  it('should match ssr result when hydrating', async () => {\n    const ensureAndUnmock = mockConsoleForHydrationErrors()\n\n    const key = createKey()\n\n    // A block fetches the data and updates the cache.\n    function Block() {\n      const { data } = useSWR(key, () => createResponse('SWR', { delay: 10 }))\n      return <div>{data || 'undefined'}</div>\n    }\n\n    const container = document.createElement('div')\n    container.innerHTML = '<div>undefined</div>'\n    await hydrateWithConfig(<Block />, container)\n    ensureAndUnmock()\n  })\n\n  // NOTE: this test is failing because it's not possible to test this behavior\n  // in JSDOM. We need to test this in a real browser.\n  it.failing(\n    'should match the ssr result when streaming and partially hydrating',\n    async () => {\n      const key = createKey()\n\n      const dataDuringHydration = {}\n\n      // A block fetches the data and updates the cache.\n      function Block({ suspense, delay, id }) {\n        const { data } = useSWR(key, () => createResponse('SWR', { delay }), {\n          suspense\n        })\n\n        // The first render is always hydration in our case.\n        if (!dataDuringHydration[id]) {\n          dataDuringHydration[id] = data || 'undefined'\n        }\n\n        return <div>{data || 'undefined'}</div>\n      }\n\n      // In this example, a will be hydrated first and b will still be streamed.\n      // When a is hydrated, it will update the client cache to SWR, and when\n      // b is being hydrated, it should NOT read that cache.\n      renderWithConfig(\n        <>\n          <Block id=\"a\" suspense={false} delay={10} />\n          <Suspense fallback={null}>\n            <Block id=\"b\" suspense={true} delay={20} />\n          </Suspense>\n        </>\n      )\n\n      // The SSR result will always be 2 undefined values because data fetching won't\n      // happen on the server:\n      //   <div>undefined</div>\n      //   <div>undefined</div>\n\n      // Wait for streaming to finish.\n      await act(() => sleep(50))\n\n      expect(dataDuringHydration).toEqual({\n        a: 'undefined',\n        b: 'undefined'\n      })\n    }\n  )\n})\n"
  },
  {
    "path": "test/use-swr-subscription.test.tsx",
    "content": "import { fireEvent, screen } from '@testing-library/react'\nimport { sleep, renderWithConfig, createKey } from './utils'\nimport useSWRSubscription from 'swr/subscription'\nimport useSWR from 'swr'\nimport { useEffect, useState, act } from 'react'\nimport { ErrorBoundary } from 'react-error-boundary'\n\ndescribe('useSWRSubscription', () => {\n  it('should update the state', async () => {\n    const swrKey = createKey()\n\n    let intervalId\n    let res = 0\n    function subscribe(key, { next }) {\n      intervalId = setInterval(() => {\n        if (res === 3) {\n          const err = new Error(key)\n          next(err)\n        } else {\n          next(undefined, key + res)\n        }\n        res++\n      }, 100)\n\n      return () => {}\n    }\n\n    function Page() {\n      const { data, error } = useSWRSubscription(swrKey, subscribe, {\n        fallbackData: 'fallback'\n      })\n      return (\n        <>\n          <div data-testid=\"data\">{'data:' + data}</div>\n          <div data-testid=\"error\">\n            {'error:' + (error ? error.message : '')}\n          </div>\n        </>\n      )\n    }\n\n    renderWithConfig(<Page />)\n    await act(() => sleep(10))\n    screen.getByText(`data:fallback`)\n    screen.getByText('error:')\n    await act(() => sleep(100))\n    screen.getByText(`data:${swrKey}0`)\n    screen.getByText('error:')\n    await act(() => sleep(100))\n    screen.getByText(`data:${swrKey}1`)\n    screen.getByText('error:')\n    await act(() => sleep(100))\n    screen.getByText(`data:${swrKey}2`)\n    screen.getByText('error:')\n    await act(() => sleep(100))\n    // error occurred, error arrives instead of data 3\n    screen.getByText(`data:${swrKey}2`)\n    screen.getByText(`error:${swrKey}`)\n    await act(() => sleep(100))\n    screen.getByText(`data:${swrKey}4`)\n    screen.getByText('error:')\n    clearInterval(intervalId)\n    await sleep(100)\n    screen.getByText(`error:`)\n  })\n\n  it('should pass the origin keys', async () => {\n    const swrKey = createKey()\n    let intervalId\n    let res = 0\n    function subscribe(key, { next }) {\n      intervalId = setInterval(() => {\n        if (res === 3) {\n          const err = new Error(key)\n          next(err)\n        } else {\n          next(undefined, key[0] + res)\n        }\n        res++\n      }, 100)\n\n      return () => {}\n    }\n\n    function Page() {\n      const { data, error } = useSWRSubscription(() => [swrKey], subscribe, {\n        fallbackData: 'fallback'\n      })\n      return (\n        <>\n          <div data-testid=\"data\">{'data:' + data}</div>\n          <div data-testid=\"error\">\n            {'error:' + (error ? error.message : '')}\n          </div>\n        </>\n      )\n    }\n    renderWithConfig(<Page />)\n    await act(() => sleep(10))\n    screen.getByText(`data:fallback`)\n    screen.getByText('error:')\n    await act(() => sleep(100))\n    screen.getByText(`data:${swrKey}0`)\n    screen.getByText('error:')\n    await act(() => sleep(100))\n    screen.getByText(`data:${swrKey}1`)\n    screen.getByText('error:')\n    await act(() => sleep(100))\n    screen.getByText(`data:${swrKey}2`)\n    screen.getByText('error:')\n    await act(() => sleep(100))\n    // error occurred, error arrives instead of data 3\n    screen.getByText(`data:${swrKey}2`)\n    screen.getByText(`error:${swrKey}`)\n    await act(() => sleep(100))\n    screen.getByText(`data:${swrKey}4`)\n    screen.getByText('error:')\n    clearInterval(intervalId)\n    await sleep(100)\n    screen.getByText(`error:`)\n  })\n\n  it('should support updating keys', async () => {\n    const swrKey = createKey()\n    const subscriptions: string[] = []\n\n    function subscribe(key, { next }) {\n      subscriptions.push(key)\n      next(undefined, key)\n      return () => {}\n    }\n\n    function Page() {\n      const [key, setKey] = useState(undefined)\n      const { data } = useSWRSubscription(key, subscribe, {\n        fallbackData: 'fallback'\n      })\n\n      useEffect(() => {\n        const timeout = setTimeout(() => {\n          setKey(swrKey)\n        }, 100)\n        return () => clearTimeout(timeout)\n      }, [])\n\n      return <div data-testid=\"data\">{'data:' + data}</div>\n    }\n\n    renderWithConfig(<Page />)\n\n    await screen.findByText(`data:fallback`)\n    await act(() => sleep(100))\n    await screen.findByText(`data:` + swrKey)\n\n    // `undefined` should not trigger a subscription.\n    expect(subscriptions).toEqual([swrKey])\n  })\n\n  it('should deduplicate subscriptions', async () => {\n    const swrKey = createKey()\n\n    let subscriptionCount = 0\n\n    function subscribe(key, { next }) {\n      ++subscriptionCount\n      let res = 0\n      const intervalId = setInterval(() => {\n        if (res === 3) {\n          const err = new Error(key + 'error')\n          next(err)\n        } else {\n          next(undefined, key + res)\n        }\n        res++\n      }, 100)\n\n      return () => {\n        clearInterval(intervalId)\n      }\n    }\n\n    function Page() {\n      const { data, error } = useSWRSubscription(swrKey, subscribe, {\n        fallbackData: 'fallback'\n      })\n      useSWRSubscription(swrKey, subscribe)\n      useSWRSubscription(swrKey, subscribe)\n\n      return <div>{error ? error.message : data}</div>\n    }\n\n    renderWithConfig(<Page />)\n    await act(() => sleep(10))\n    screen.getByText(`fallback`)\n    await act(() => sleep(100))\n    screen.getByText(`${swrKey}0`)\n    await act(() => sleep(100))\n    screen.getByText(`${swrKey}1`)\n    await act(() => sleep(100))\n    screen.getByText(`${swrKey}2`)\n\n    expect(subscriptionCount).toBe(1)\n  })\n\n  it('should not conflict with useSWR state', async () => {\n    const swrKey = createKey()\n\n    function subscribe(key, { next }) {\n      let res = 0\n      const intervalId = setInterval(() => {\n        if (res === 3) {\n          const err = new Error(key + 'error')\n          next(err)\n        } else {\n          next(undefined, key + res)\n        }\n        res++\n      }, 100)\n\n      return () => {\n        clearInterval(intervalId)\n      }\n    }\n\n    function Page() {\n      const { data, error } = useSWRSubscription(swrKey, subscribe, {\n        fallbackData: 'fallback'\n      })\n      const { data: swrData } = useSWR(swrKey, () => 'swr')\n      return (\n        <div>\n          {swrData}:{error ? error.message : data}\n        </div>\n      )\n    }\n\n    renderWithConfig(<Page />)\n    await act(() => sleep(10))\n    screen.getByText(`swr:fallback`)\n    await act(() => sleep(100))\n    screen.getByText(`swr:${swrKey}0`)\n    await act(() => sleep(100))\n    screen.getByText(`swr:${swrKey}1`)\n    await act(() => sleep(100))\n    screen.getByText(`swr:${swrKey}2`)\n  })\n\n  it('should support singleton subscription', async () => {\n    const placeholderFn: (data: number) => void = () => {}\n    let callback: (data: number) => void = placeholderFn\n    const sub = (fn: (data: number) => void) => {\n      callback = fn\n      return () => {\n        callback = placeholderFn\n      }\n    }\n    const emit = (data: number) => {\n      callback(data)\n    }\n    const useSubData = (key: number) =>\n      useSWRSubscription(key.toString(), (_, { next }) =>\n        sub(data => next(null, data + key))\n      )\n    const App = () => {\n      const [key, setKey] = useState(0)\n      const { data } = useSubData(key)\n      useEffect(() => {\n        callback(1)\n      }, [])\n      return (\n        <div className=\"App\">\n          <p>key: {key}</p>\n          <p className=\"read-the-docs\">data: {data}</p>\n          <button\n            onClick={() => {\n              setKey(value => value + 1)\n              setTimeout(() => {\n                emit(2)\n              }, 100)\n            }}\n          >\n            add\n          </button>\n        </div>\n      )\n    }\n    renderWithConfig(<App />)\n    await screen.findByText(`key: 0`)\n    await screen.findByText(`data: 1`)\n    fireEvent.click(screen.getByText('add'))\n    await act(() => sleep(100))\n    await screen.findByText(`key: 1`)\n    await screen.findByText(`data: 3`)\n  })\n\n  it('should require a dispose function', async () => {\n    jest.spyOn(console, 'error').mockImplementation(() => {})\n\n    const swrKey = createKey()\n\n    function subscribe() {\n      return 'no-dispose'\n    }\n\n    function Page() {\n      useSWRSubscription(swrKey, subscribe)\n      return null\n    }\n\n    renderWithConfig(\n      <ErrorBoundary\n        fallbackRender={({ error }) => {\n          return <div>{error.message}</div>\n        }}\n      >\n        <Page />\n      </ErrorBoundary>\n    )\n\n    await screen.findByText(\n      'The `subscribe` function must return a function to unsubscribe.'\n    )\n  })\n})\n"
  },
  {
    "path": "test/use-swr-suspense.test.tsx",
    "content": "import { fireEvent, screen } from '@testing-library/react'\nimport { Profiler, act } from 'react'\nimport { Suspense, useReducer, useState } from 'react'\nimport useSWR, { mutate } from 'swr'\nimport {\n  createKey,\n  createResponse,\n  itShouldSkipForReactCanary,\n  renderWithConfig,\n  renderWithGlobalCache,\n  sleep\n} from './utils'\nimport { ErrorBoundary } from 'react-error-boundary'\n\ndescribe('useSWR - suspense', () => {\n  afterEach(() => {\n    jest.clearAllMocks()\n    jest.restoreAllMocks()\n  })\n\n  itShouldSkipForReactCanary('should render fallback', async () => {\n    const key = createKey()\n    function Section() {\n      const { data } = useSWR(key, () => createResponse('SWR'), {\n        suspense: true\n      })\n      return <div>{data}</div>\n    }\n\n    renderWithConfig(\n      <Suspense fallback={<div>fallback</div>}>\n        <Section />\n      </Suspense>\n    )\n\n    // hydration\n    screen.getByText('fallback')\n    await screen.findByText('SWR')\n  })\n\n  itShouldSkipForReactCanary(\n    'should render multiple SWR fallbacks',\n    async () => {\n      const key = createKey()\n      function Section() {\n        const { data: v1 } = useSWR<number>(\n          key,\n          () => createResponse(1, { delay: 50 }),\n          {\n            suspense: true\n          }\n        )\n        const { data: v2 } = useSWR<number>(\n          'suspense-3',\n          () => createResponse(2, { delay: 50 }),\n          {\n            suspense: true\n          }\n        )\n        return <div>{v1 + v2}</div>\n      }\n\n      renderWithConfig(\n        <Suspense fallback={<div>fallback</div>}>\n          <Section />\n        </Suspense>\n      )\n\n      // hydration\n      screen.getByText('fallback')\n      await act(() => sleep(70))\n      screen.getByText('fallback')\n      await act(() => sleep(70))\n      screen.getByText('3')\n    }\n  )\n\n  itShouldSkipForReactCanary('should work for non-promises', async () => {\n    const key = createKey()\n    function Section() {\n      const { data } = useSWR(key, () => 'hello', {\n        suspense: true\n      })\n      return <div>{data}</div>\n    }\n    renderWithConfig(\n      <Suspense fallback={<div>fallback</div>}>\n        <Section />\n      </Suspense>\n    )\n\n    await screen.findByText('hello')\n  })\n\n  itShouldSkipForReactCanary('should throw errors', async () => {\n    jest.spyOn(console, 'error').mockImplementation(() => {})\n    const key = createKey()\n    function Section() {\n      const { data } = useSWR<any>(\n        key,\n        () => createResponse(new Error('error')),\n        {\n          suspense: true\n        }\n      )\n      return <div>{data}</div>\n    }\n\n    // https://reactjs.org/docs/concurrent-mode-suspense.html#handling-errors\n    renderWithConfig(\n      <ErrorBoundary fallback={<div>error boundary</div>}>\n        <Suspense fallback={<div>fallback</div>}>\n          <Section />\n        </Suspense>\n      </ErrorBoundary>\n    )\n\n    // hydration\n    screen.getByText('fallback')\n    await screen.findByText('error boundary')\n  })\n\n  itShouldSkipForReactCanary(\n    'should render cached data with error',\n    async () => {\n      const key = createKey()\n      mutate(key, 'hello')\n\n      function Section() {\n        const { data, error } = useSWR(\n          // this value is cached\n          key,\n          () => createResponse(new Error('error')),\n          {\n            suspense: true\n          }\n        )\n        return (\n          <div>\n            {data}, {error ? error.message : null}\n          </div>\n        )\n      }\n\n      renderWithGlobalCache(\n        <Suspense fallback={<div>fallback</div>}>\n          <Section />\n        </Suspense>\n      )\n\n      screen.getByText('hello,') // directly from cache\n      await screen.findByText('hello, error') // get the error with cache\n    }\n  )\n\n  itShouldSkipForReactCanary(\n    'should not fetch when cached data is present and `revalidateIfStale` is false',\n    async () => {\n      const key = createKey()\n      mutate(key, 'cached')\n\n      let fetchCount = 0\n\n      function Section() {\n        const { data } = useSWR(key, () => createResponse(++fetchCount), {\n          suspense: true,\n          revalidateIfStale: false\n        })\n        return <div>{data}</div>\n      }\n\n      renderWithGlobalCache(\n        <Suspense fallback={<div>fallback</div>}>\n          <Section />\n        </Suspense>\n      )\n\n      screen.getByText('cached')\n      await act(() => sleep(50)) // Wait to confirm fetch is not triggered\n      expect(fetchCount).toBe(0)\n    }\n  )\n\n  itShouldSkipForReactCanary('should pause when key changes', async () => {\n    // fixes https://github.com/vercel/swr/issues/57\n    // initialKey' -> undefined -> updatedKey\n    const initialKey = createKey()\n    const updatedKey = createKey()\n    const onRender = jest.fn()\n    function Section() {\n      const [key, setKey] = useState(initialKey)\n      const { data } = useSWR(key, k => createResponse(k), {\n        suspense: true\n      })\n      return (\n        <>\n          <div>data: {data}</div>\n          <button onClick={() => setKey(updatedKey)}>change</button>\n        </>\n      )\n    }\n\n    renderWithConfig(\n      <Suspense\n        fallback={\n          <Profiler id={initialKey} onRender={onRender}>\n            <div>fallback</div>\n          </Profiler>\n        }\n      >\n        <Section />\n      </Suspense>\n    )\n    await screen.findByText('fallback')\n    await screen.findByText(`data: ${initialKey}`)\n    fireEvent.click(screen.getByText('change'))\n    await screen.findByText('fallback')\n    await screen.findByText(`data: ${updatedKey}`)\n    expect(onRender).toHaveBeenCalledTimes(2)\n  })\n\n  itShouldSkipForReactCanary(\n    'should render correctly when key changes (but with same response data)',\n    async () => {\n      // https://github.com/vercel/swr/issues/1056\n      const renderedResults = []\n      const baseKey = createKey()\n      function Section() {\n        const [key, setKey] = useState(1)\n        const { data } = useSWR(\n          `${baseKey}-${key}`,\n          () => createResponse('123'),\n          {\n            suspense: true\n          }\n        )\n        if (`${data},${key}` !== renderedResults[renderedResults.length - 1]) {\n          renderedResults.push(`${data},${key}`)\n        }\n        return <div onClick={() => setKey(v => v + 1)}>{`${data},${key}`}</div>\n      }\n\n      renderWithConfig(\n        <Suspense fallback={<div>fallback</div>}>\n          <Section />\n        </Suspense>\n      )\n\n      await screen.findByText('123,1')\n\n      fireEvent.click(screen.getByText('123,1'))\n\n      await screen.findByText('123,2')\n\n      expect(renderedResults).toEqual(['123,1', '123,2'])\n    }\n  )\n\n  itShouldSkipForReactCanary(\n    'should render correctly when key changes (from null to valid key)',\n    async () => {\n      // https://github.com/vercel/swr/issues/1836\n      const renderedResults = []\n      const baseKey = createKey()\n      let setData: any = () => {}\n      const Result = ({ query }: { query: string }) => {\n        const { data } = useSWR(\n          query ? `${baseKey}-${query}` : null,\n          key => createResponse(key, { delay: 200 }),\n          {\n            suspense: true\n          }\n        )\n        if (`${data}` !== renderedResults[renderedResults.length - 1]) {\n          if (data === undefined) {\n            renderedResults.push(`${baseKey}-nodata`)\n          } else {\n            renderedResults.push(`${data}`)\n          }\n        }\n        return <div>{data ? data : `${baseKey}-nodata`}</div>\n      }\n      const App = () => {\n        const [query, setQuery] = useState('123')\n        if (setData !== setQuery) {\n          setData = setQuery\n        }\n        return (\n          <>\n            <br />\n            <br />\n            <Suspense fallback={null}>\n              <Result query={query}></Result>\n            </Suspense>\n          </>\n        )\n      }\n\n      renderWithConfig(<App />)\n\n      await screen.findByText(`${baseKey}-123`)\n\n      act(() => setData(''))\n      await screen.findByText(`${baseKey}-nodata`)\n\n      act(() => setData('456'))\n      await screen.findByText(`${baseKey}-456`)\n\n      expect(renderedResults).toEqual([\n        `${baseKey}-123`,\n        `${baseKey}-nodata`,\n        `${baseKey}-456`\n      ])\n    }\n  )\n\n  itShouldSkipForReactCanary('should render initial data if set', async () => {\n    const fetcher = jest.fn(() => 'SWR')\n\n    const key = createKey()\n    function Page() {\n      const { data } = useSWR(key, fetcher, {\n        fallbackData: 'Initial',\n        suspense: true\n      })\n      return <div>hello, {data}</div>\n    }\n\n    renderWithConfig(\n      <Suspense fallback={<div>fallback</div>}>\n        <Page />\n      </Suspense>\n    )\n\n    expect(fetcher).not.toHaveBeenCalled()\n    screen.getByText('hello, Initial')\n  })\n\n  itShouldSkipForReactCanary(\n    'should avoid unnecessary re-renders',\n    async () => {\n      let renderCount = 0\n      let startRenderCount = 0\n      const key = createKey()\n      function Section() {\n        ++startRenderCount\n        const { data } = useSWR(key, () => createResponse('SWR'), {\n          suspense: true\n        })\n        ++renderCount\n        return <div>{data}</div>\n      }\n\n      renderWithConfig(\n        <Suspense fallback={<div>fallback</div>}>\n          <Section />\n        </Suspense>\n      )\n\n      // hydration\n      screen.getByText('fallback')\n      await screen.findByText('SWR')\n      await act(() => sleep(50)) // wait a moment to observe unnecessary renders\n      expect(startRenderCount).toBe(2) // fallback + data\n      expect(renderCount).toBe(1) // data\n    }\n  )\n\n  itShouldSkipForReactCanary(\n    'should return `undefined` data for falsy key',\n    async () => {\n      const key = createKey()\n      const Section = ({ trigger }: { trigger: boolean }) => {\n        const { data } = useSWR(\n          trigger ? key : null,\n          () => createResponse('SWR'),\n          {\n            suspense: true\n          }\n        )\n        return <div>{data || 'empty'}</div>\n      }\n\n      const App = () => {\n        const [trigger, toggle] = useReducer(x => !x, false)\n        return (\n          <div>\n            <button onClick={toggle}>toggle</button>\n            <Suspense fallback={<div>fallback</div>}>\n              <Section trigger={trigger} />\n            </Suspense>\n          </div>\n        )\n      }\n\n      renderWithConfig(<App />)\n\n      await screen.findByText('empty')\n\n      fireEvent.click(screen.getByRole('button'))\n\n      screen.getByText('fallback')\n\n      await screen.findByText('SWR')\n    }\n  )\n\n  itShouldSkipForReactCanary(\n    'should only render fallback once when `keepPreviousData` is set to true',\n    async () => {\n      const originKey = createKey()\n      const newKey = createKey()\n      const onRender = jest.fn()\n      const Result = ({ query }: { query: string }) => {\n        const { data } = useSWR(query, q => createResponse(q, { delay: 200 }), {\n          suspense: true,\n          keepPreviousData: true\n        })\n        return <div>data: {data}</div>\n      }\n      const App = () => {\n        const [query, setQuery] = useState(originKey)\n        return (\n          <>\n            <button onClick={() => setQuery(newKey)}>change</button>\n            <br />\n            <Suspense\n              fallback={\n                <Profiler id={originKey} onRender={onRender}>\n                  <div>loading</div>\n                </Profiler>\n              }\n            >\n              <Result query={query}></Result>\n            </Suspense>\n          </>\n        )\n      }\n      renderWithConfig(<App />)\n      await act(() => sleep(200))\n      await screen.findByText(`data: ${originKey}`)\n      fireEvent.click(screen.getByText('change'))\n      await act(() => sleep(200))\n      await screen.findByText(`data: ${newKey}`)\n      expect(onRender).toHaveBeenCalledTimes(1)\n    }\n  )\n})\n"
  },
  {
    "path": "test/utils.tsx",
    "content": "import { act, fireEvent, render } from '@testing-library/react'\nimport { SWRConfig } from 'swr'\n\nexport function sleep(time: number) {\n  return new Promise<void>(resolve => setTimeout(resolve, time))\n}\n\nexport const createResponse = <T,>(\n  response: T,\n  { delay } = { delay: 10 }\n): Promise<T> =>\n  new Promise((resolve, reject) =>\n    setTimeout(() => {\n      if (response instanceof Error) {\n        reject(response)\n      } else {\n        resolve(response)\n      }\n    }, delay)\n  )\n\nexport const nextTick = () => act(() => sleep(1))\n\nexport const focusOn = (element: any) =>\n  act(async () => {\n    fireEvent.focus(element)\n  })\n\nexport const createKey = () => 'swr-key-' + ~~(Math.random() * 1e7)\n\nconst _renderWithConfig = (\n  element: React.ReactElement,\n  config: Parameters<typeof SWRConfig>[0]['value']\n): ReturnType<typeof render> => {\n  const TestSWRConfig = ({ children }: { children: React.ReactNode }) => (\n    <SWRConfig value={config}>{children}</SWRConfig>\n  )\n  return render(element, { wrapper: TestSWRConfig })\n}\n\nexport const renderWithConfig = (\n  element: React.ReactElement,\n  config?: Parameters<typeof _renderWithConfig>[1]\n): ReturnType<typeof _renderWithConfig> => {\n  const provider = () => new Map()\n  return _renderWithConfig(element, { provider, ...config })\n}\n\nexport const renderWithGlobalCache = (\n  element: React.ReactElement,\n  config?: Parameters<typeof _renderWithConfig>[1]\n): ReturnType<typeof _renderWithConfig> => {\n  return _renderWithConfig(element, { ...config })\n}\n\nexport const hydrateWithConfig = (\n  element: React.ReactElement,\n  container: HTMLElement,\n  config?: Parameters<typeof _renderWithConfig>[1]\n): ReturnType<typeof _renderWithConfig> => {\n  const provider = () => new Map()\n  const TestSWRConfig = ({ children }: { children: React.ReactNode }) => (\n    <SWRConfig value={{ provider, ...config }}>{children}</SWRConfig>\n  )\n  return render(element, {\n    container,\n    wrapper: TestSWRConfig,\n    hydrate: true,\n    legacyRoot: process.env.TEST_REACT_LEGACY === '1'\n  })\n}\n\nexport const mockVisibilityHidden = () => {\n  const mockVisibilityState = jest.spyOn(document, 'visibilityState', 'get')\n  mockVisibilityState.mockImplementation(() => 'hidden')\n  return () => mockVisibilityState.mockRestore()\n}\n\n// Using `act()` will cause React 18 to batch updates.\n// https://github.com/reactwg/react-18/discussions/102\nexport async function executeWithoutBatching(fn: () => any) {\n  const prev = global.IS_REACT_ACT_ENVIRONMENT\n  global.IS_REACT_ACT_ENVIRONMENT = false\n  await fn()\n  global.IS_REACT_ACT_ENVIRONMENT = prev\n}\n\nexport const mockConsoleForHydrationErrors = () => {\n  jest.spyOn(console, 'error').mockImplementation(() => {})\n  return () => {\n    // It should not have any hydration warnings.\n    expect(\n      // @ts-expect-error\n      console.error.mock.calls.find(([err]) => {\n        return (\n          err?.message?.includes(\n            'Text content does not match server-rendered HTML.'\n          ) ||\n          err?.message?.includes(\n            'Hydration failed because the initial UI does not match what was rendered on the server.'\n          )\n        )\n      })\n    ).toBeFalsy()\n\n    // @ts-expect-error\n    console.error.mockRestore()\n  }\n}\n\nexport const itShouldSkipForReactCanary = (...args: Parameters<jest.It>) => {\n  if (process.env.TEST_REACT_CANARY === '1') {\n    return it.skip(...args)\n  } else {\n    return it(...args)\n  }\n}\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"declaration\": true,\n    \"esModuleInterop\": true,\n    \"jsx\": \"react\",\n    \"lib\": [\"esnext\", \"dom\"],\n    \"module\": \"NodeNext\",\n    \"moduleResolution\": \"nodenext\",\n    \"noFallthroughCasesInSwitch\": true,\n    \"noImplicitReturns\": false,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"strictBindCallApply\": true,\n    \"outDir\": \"./dist\",\n    \"rootDir\": \"./\",\n    \"strict\": true,\n    \"target\": \"ES2018\",\n    \"noEmitOnError\": true,\n    \"incremental\": true\n  },\n  \"include\": [\"./src/**/*\", \"env.d.ts\", \"eslint.config.mjs\", \"jest.config.js\"],\n  \"exclude\": [\"./**/dist\", \"examples\"],\n  \"watchOptions\": {\n    \"watchFile\": \"useFsEvents\",\n    \"watchDirectory\": \"useFsEvents\",\n    \"fallbackPolling\": \"dynamicPriority\"\n  }\n}\n"
  }
]