Repository: vercel/swr
Branch: main
Commit: 5fa29522f196
Files: 344
Total size: 663.2 KB
Directory structure:
gitextract__gxvvg_b/
├── .codesandbox/
│ └── ci.json
├── .editorconfig
├── .github/
│ ├── CODEOWNERS
│ ├── CODE_OF_CONDUCT.md
│ ├── CONTRIBUTING.md
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug_report.md
│ │ └── config.yml
│ ├── SECURITY.md
│ └── workflows/
│ ├── install/
│ │ └── action.yml
│ ├── test-canary.yml
│ ├── test-legacy-react.yml
│ ├── test-release.yml
│ └── trigger-release.yml
├── .gitignore
├── .husky/
│ └── pre-commit
├── .npmrc
├── .swcrc
├── LICENSE
├── README.md
├── _internal/
│ └── package.json
├── e2e/
│ ├── site/
│ │ ├── README.md
│ │ ├── app/
│ │ │ ├── basic-ssr/
│ │ │ │ ├── block.tsx
│ │ │ │ └── page.tsx
│ │ │ ├── concurrent-transition/
│ │ │ │ ├── page.tsx
│ │ │ │ └── transition-demo.tsx
│ │ │ ├── issue-2702/
│ │ │ │ ├── page.tsx
│ │ │ │ └── reproduction.tsx
│ │ │ ├── layout.tsx
│ │ │ ├── mutate-server-action/
│ │ │ │ ├── action.tsx
│ │ │ │ └── page.tsx
│ │ │ ├── page.tsx
│ │ │ ├── partially-hydrate/
│ │ │ │ ├── layout.tsx
│ │ │ │ ├── loading.tsx
│ │ │ │ ├── page.tsx
│ │ │ │ └── use-data.tsx
│ │ │ ├── perf/
│ │ │ │ └── page.tsx
│ │ │ ├── react-server-entry/
│ │ │ │ └── page.tsx
│ │ │ ├── render-count/
│ │ │ │ └── page.tsx
│ │ │ ├── render-preload-avoid-waterfall/
│ │ │ │ └── page.tsx
│ │ │ ├── render-preload-basic/
│ │ │ │ └── page.tsx
│ │ │ ├── render-promise-suspense-error/
│ │ │ │ └── page.tsx
│ │ │ ├── render-promise-suspense-resolve/
│ │ │ │ └── page.tsx
│ │ │ ├── render-promise-suspense-shared/
│ │ │ │ └── page.tsx
│ │ │ ├── render-suspense-avoid-rerender/
│ │ │ │ └── page.tsx
│ │ │ ├── render-suspense-cached-error/
│ │ │ │ └── page.tsx
│ │ │ ├── render-suspense-error/
│ │ │ │ └── page.tsx
│ │ │ ├── render-suspense-fallback/
│ │ │ │ └── page.tsx
│ │ │ ├── render-suspense-fetcher/
│ │ │ │ └── page.tsx
│ │ │ ├── render-suspense-infinite-preload/
│ │ │ │ └── page.tsx
│ │ │ ├── render-suspense-initial-data/
│ │ │ │ └── page.tsx
│ │ │ ├── render-suspense-keep-previous/
│ │ │ │ └── page.tsx
│ │ │ ├── render-suspense-key-change/
│ │ │ │ └── page.tsx
│ │ │ ├── render-suspense-multiple-fallbacks/
│ │ │ │ └── page.tsx
│ │ │ ├── render-suspense-no-revalidate/
│ │ │ │ └── page.tsx
│ │ │ ├── render-suspense-non-promise/
│ │ │ │ └── page.tsx
│ │ │ ├── render-suspense-null-key/
│ │ │ │ └── page.tsx
│ │ │ ├── render-suspense-same-data/
│ │ │ │ └── page.tsx
│ │ │ ├── server-prefetch-warning/
│ │ │ │ └── page.tsx
│ │ │ ├── suspense-after-preload/
│ │ │ │ ├── page.tsx
│ │ │ │ └── remote-data.tsx
│ │ │ ├── suspense-fallback/
│ │ │ │ ├── layout.tsx
│ │ │ │ └── promise/
│ │ │ │ └── page.tsx
│ │ │ ├── suspense-infinite-get-key/
│ │ │ │ └── page.tsx
│ │ │ ├── suspense-retry/
│ │ │ │ ├── manual-retry.tsx
│ │ │ │ ├── page.tsx
│ │ │ │ └── use-remote-data.ts
│ │ │ └── suspense-undefined-key/
│ │ │ └── page.tsx
│ │ ├── component/
│ │ │ ├── manual-retry-mutate.tsx
│ │ │ ├── manual-retry.tsx
│ │ │ ├── only-render-in-client.tsx
│ │ │ └── use-remote-data.ts
│ │ ├── lib/
│ │ │ ├── sleep.ts
│ │ │ └── use-debug-history.ts
│ │ ├── next-env.d.ts
│ │ ├── next.config.js
│ │ ├── package.json
│ │ ├── pages/
│ │ │ ├── api/
│ │ │ │ ├── data.ts
│ │ │ │ └── retry.ts
│ │ │ ├── suspense-retry-19.tsx
│ │ │ └── suspense-retry-mutate.tsx
│ │ └── tsconfig.json
│ └── test/
│ ├── concurrent-transition.test.ts
│ ├── initial-render.test.ts
│ ├── issue-2702-too-many-hooks.ts
│ ├── mutate-server-action.test.ts
│ ├── perf.test.ts
│ ├── preload-scenarios.test.ts
│ ├── promise-scenarios.test.ts
│ ├── server-prefetch-warning.test.ts
│ ├── stream-ssr.test.ts
│ ├── suspense-fallback.test.ts
│ ├── suspense-scenarios.test.ts
│ ├── suspense-undefined-key.test.ts
│ └── tsconfig.json
├── eslint.config.mjs
├── examples/
│ ├── .eslintrc
│ ├── api-hooks/
│ │ ├── README.md
│ │ ├── hooks/
│ │ │ ├── use-projects.js
│ │ │ └── use-repository.js
│ │ ├── libs/
│ │ │ └── fetch.js
│ │ ├── package.json
│ │ └── pages/
│ │ ├── [user]/
│ │ │ └── [repo].js
│ │ ├── api/
│ │ │ └── data.js
│ │ └── index.js
│ ├── autocomplete-suggestions/
│ │ ├── README.md
│ │ ├── libs/
│ │ │ └── fetcher.js
│ │ ├── package.json
│ │ └── pages/
│ │ ├── api/
│ │ │ └── suggestions.js
│ │ └── index.js
│ ├── axios/
│ │ ├── README.md
│ │ ├── libs/
│ │ │ └── useRequest.js
│ │ ├── package.json
│ │ └── pages/
│ │ ├── [user]/
│ │ │ └── [repo].js
│ │ ├── api/
│ │ │ └── data.js
│ │ └── index.js
│ ├── axios-typescript/
│ │ ├── README.md
│ │ ├── libs/
│ │ │ └── useRequest.ts
│ │ ├── next-env.d.ts
│ │ ├── package.json
│ │ ├── pages/
│ │ │ ├── [user]/
│ │ │ │ └── [repo].tsx
│ │ │ ├── api/
│ │ │ │ └── data.ts
│ │ │ └── index.tsx
│ │ └── tsconfig.json
│ ├── basic/
│ │ ├── README.md
│ │ ├── libs/
│ │ │ └── fetch.js
│ │ ├── package.json
│ │ └── pages/
│ │ ├── [user]/
│ │ │ └── [repo].js
│ │ ├── api/
│ │ │ └── data.js
│ │ └── index.js
│ ├── basic-typescript/
│ │ ├── README.md
│ │ ├── libs/
│ │ │ └── fetch.ts
│ │ ├── next-env.d.ts
│ │ ├── package.json
│ │ ├── pages/
│ │ │ ├── [user]/
│ │ │ │ └── [repo].tsx
│ │ │ ├── api/
│ │ │ │ └── data.ts
│ │ │ └── index.tsx
│ │ └── tsconfig.json
│ ├── focus-revalidate/
│ │ ├── README.md
│ │ ├── components/
│ │ │ └── button.js
│ │ ├── libs/
│ │ │ ├── auth.js
│ │ │ └── fetch.js
│ │ ├── package.json
│ │ └── pages/
│ │ ├── api/
│ │ │ └── user.js
│ │ └── index.js
│ ├── global-fetcher/
│ │ ├── README.md
│ │ ├── libs/
│ │ │ └── fetch.js
│ │ ├── package.json
│ │ └── pages/
│ │ ├── [user]/
│ │ │ └── [repo].js
│ │ ├── _app.js
│ │ ├── api/
│ │ │ └── data.js
│ │ └── index.js
│ ├── infinite/
│ │ ├── README.md
│ │ ├── libs/
│ │ │ └── fetch.js
│ │ ├── package.json
│ │ └── pages/
│ │ └── index.js
│ ├── infinite-scroll/
│ │ ├── README.md
│ │ ├── hooks/
│ │ │ └── useOnScreen.js
│ │ ├── libs/
│ │ │ └── fetch.js
│ │ ├── package.json
│ │ └── pages/
│ │ └── index.js
│ ├── local-state-sharing/
│ │ ├── README.md
│ │ ├── libs/
│ │ │ └── store.js
│ │ ├── package.json
│ │ └── pages/
│ │ └── index.js
│ ├── optimistic-ui/
│ │ ├── README.md
│ │ ├── libs/
│ │ │ └── fetch.js
│ │ ├── package.json
│ │ ├── pages/
│ │ │ ├── _app.js
│ │ │ ├── api/
│ │ │ │ └── todos.js
│ │ │ └── index.js
│ │ └── styles.css
│ ├── optimistic-ui-immer/
│ │ ├── README.md
│ │ ├── libs/
│ │ │ └── fetch.js
│ │ ├── package.json
│ │ └── pages/
│ │ ├── api/
│ │ │ └── data.js
│ │ └── index.js
│ ├── prefetch-preload/
│ │ ├── README.md
│ │ ├── libs/
│ │ │ └── fetch.js
│ │ ├── package.json
│ │ └── pages/
│ │ ├── [user]/
│ │ │ └── [repo].js
│ │ ├── api/
│ │ │ └── data.js
│ │ └── index.js
│ ├── refetch-interval/
│ │ ├── README.md
│ │ ├── components/
│ │ │ └── button.js
│ │ ├── libs/
│ │ │ └── fetch.js
│ │ ├── package.json
│ │ └── pages/
│ │ ├── api/
│ │ │ └── data.js
│ │ └── index.js
│ ├── server-render/
│ │ ├── README.md
│ │ ├── libs/
│ │ │ └── fetcher.js
│ │ ├── package.json
│ │ └── pages/
│ │ ├── [pokemon].js
│ │ └── index.js
│ ├── storage-tab-sync/
│ │ ├── README.md
│ │ ├── libs/
│ │ │ └── storage.js
│ │ ├── package.json
│ │ └── pages/
│ │ └── index.js
│ ├── subscription/
│ │ ├── README.md
│ │ ├── package.json
│ │ └── pages/
│ │ └── index.js
│ ├── suspense/
│ │ ├── README.md
│ │ ├── app/
│ │ │ ├── layout.jsx
│ │ │ └── rsc/
│ │ │ ├── [user]/
│ │ │ │ └── [repo]/
│ │ │ │ ├── error.jsx
│ │ │ │ ├── loading.jsx
│ │ │ │ ├── page.jsx
│ │ │ │ └── repo.jsx
│ │ │ ├── loading.jsx
│ │ │ ├── page.jsx
│ │ │ └── repos.jsx
│ │ ├── components/
│ │ │ └── error-handling.js
│ │ ├── libs/
│ │ │ └── fetch.js
│ │ ├── next-env.d.ts
│ │ ├── package.json
│ │ └── pages/
│ │ ├── [user]/
│ │ │ └── [repo].js
│ │ ├── api/
│ │ │ └── data.js
│ │ └── index.js
│ ├── suspense-global/
│ │ ├── README.md
│ │ ├── components/
│ │ │ └── error-handling.ts
│ │ ├── global-swr-config.tsx
│ │ ├── libs/
│ │ │ └── fetch.ts
│ │ ├── next-env.d.ts
│ │ ├── package.json
│ │ ├── pages/
│ │ │ ├── [user]/
│ │ │ │ ├── [repo].tsx
│ │ │ │ └── detail.tsx
│ │ │ ├── _app.tsx
│ │ │ ├── api/
│ │ │ │ └── data.ts
│ │ │ ├── index.tsx
│ │ │ └── repos.tsx
│ │ └── tsconfig.json
│ └── suspense-retry/
│ ├── app/
│ │ ├── api/
│ │ │ └── route.ts
│ │ ├── layout.tsx
│ │ ├── manual-retry.tsx
│ │ ├── page.tsx
│ │ └── use-remote-data.ts
│ ├── next-env.d.ts
│ ├── next.config.js
│ ├── package.json
│ ├── pages/
│ │ └── retry.tsx
│ └── tsconfig.json
├── immutable/
│ └── package.json
├── infinite/
│ └── package.json
├── jest.config.build.js
├── jest.config.js
├── mutation/
│ └── package.json
├── package.json
├── playwright.config.js
├── pnpm-workspace.yaml
├── scripts/
│ └── bump-next-version.js
├── src/
│ ├── _internal/
│ │ ├── constants.ts
│ │ ├── events.ts
│ │ ├── index.react-server.ts
│ │ ├── index.ts
│ │ ├── types.ts
│ │ └── utils/
│ │ ├── cache.ts
│ │ ├── config-context.ts
│ │ ├── config.ts
│ │ ├── devtools.ts
│ │ ├── env.ts
│ │ ├── global-state.ts
│ │ ├── hash.ts
│ │ ├── helper.ts
│ │ ├── merge-config.ts
│ │ ├── middleware-preset.ts
│ │ ├── mutate.ts
│ │ ├── normalize-args.ts
│ │ ├── preload.ts
│ │ ├── resolve-args.ts
│ │ ├── serialize.ts
│ │ ├── shared.ts
│ │ ├── subscribe-key.ts
│ │ ├── timestamp.ts
│ │ ├── use-swr-config.ts
│ │ ├── web-preset.ts
│ │ └── with-middleware.ts
│ ├── immutable/
│ │ └── index.ts
│ ├── index/
│ │ ├── config.ts
│ │ ├── index.react-server.ts
│ │ ├── index.ts
│ │ ├── serialize.ts
│ │ └── use-swr.ts
│ ├── infinite/
│ │ ├── index.react-server.ts
│ │ ├── index.ts
│ │ ├── serialize.ts
│ │ └── types.ts
│ ├── mutation/
│ │ ├── index.ts
│ │ ├── state.ts
│ │ └── types.ts
│ └── subscription/
│ ├── index.ts
│ └── types.ts
├── subscription/
│ └── package.json
├── test/
│ ├── jest-setup.ts
│ ├── tsconfig.json
│ ├── type/
│ │ ├── .eslintrc
│ │ ├── config.tsx
│ │ ├── fetcher.ts
│ │ ├── helper-types.tsx
│ │ ├── internal.tsx
│ │ ├── mutate.ts
│ │ ├── mutation.ts
│ │ ├── option-fetcher.ts
│ │ ├── preload.ts
│ │ ├── subscription.ts
│ │ ├── suspense/
│ │ │ ├── helper-types.tsx
│ │ │ ├── suspense.ts
│ │ │ └── tsconfig.json
│ │ ├── trigger.ts
│ │ ├── tsconfig.json
│ │ └── utils.ts
│ ├── unit/
│ │ ├── serialize.test.ts
│ │ ├── utils.test.tsx
│ │ └── web-preset.test.ts
│ ├── use-swr-cache.test.tsx
│ ├── use-swr-concurrent-rendering.test.tsx
│ ├── use-swr-config-callbacks.test.tsx
│ ├── use-swr-config.test.tsx
│ ├── use-swr-context-config.test.tsx
│ ├── use-swr-devtools.test.tsx
│ ├── use-swr-error.test.tsx
│ ├── use-swr-fetcher.test.tsx
│ ├── use-swr-focus.test.tsx
│ ├── use-swr-immutable.test.tsx
│ ├── use-swr-infinite-preload.test.tsx
│ ├── use-swr-infinite.test.tsx
│ ├── use-swr-integration.test.tsx
│ ├── use-swr-key.test.tsx
│ ├── use-swr-laggy.test.tsx
│ ├── use-swr-legacy-react.test.tsx
│ ├── use-swr-loading.test.tsx
│ ├── use-swr-local-mutation.test.tsx
│ ├── use-swr-middlewares.test.tsx
│ ├── use-swr-node-env.test.tsx
│ ├── use-swr-offline.test.tsx
│ ├── use-swr-preload.test.tsx
│ ├── use-swr-promise.test.tsx
│ ├── use-swr-reconnect.test.tsx
│ ├── use-swr-refresh.test.tsx
│ ├── use-swr-remote-mutation.test.tsx
│ ├── use-swr-revalidate.test.tsx
│ ├── use-swr-server.test.tsx
│ ├── use-swr-streaming-ssr.test.tsx
│ ├── use-swr-subscription.test.tsx
│ ├── use-swr-suspense.test.tsx
│ └── utils.tsx
└── tsconfig.json
================================================
FILE CONTENTS
================================================
================================================
FILE: .codesandbox/ci.json
================================================
{
"sandboxes": ["swr-basic-p7dg6", "swr-states-4une7", "swr-infinite-jb5bm", "swr-ssr-j9b2y"],
"node": "18",
"installCommand": "csb:install",
"buildCommand": "csb:build"
}
================================================
FILE: .editorconfig
================================================
root = true
[*.{js,ts,jsx,tsx}]
indent_size = 2
indent_style = space
================================================
FILE: .github/CODEOWNERS
================================================
* @shuding @huozhi
================================================
FILE: .github/CODE_OF_CONDUCT.md
================================================
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to make participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, sex characteristics, gender identity and expression,
level of experience, education, socio-economic status, nationality, personal
appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at coc@vercel.com. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality concerning the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see
https://www.contributor-covenant.org/faq
================================================
FILE: .github/CONTRIBUTING.md
================================================
# SWR Contribution Guidelines
Thank you for reading this guide and we appreciate any contribution.
## Ask a Question
You 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.
## Report a Bug
Whenever 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.
If 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.
## Request for a New Feature
For 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.
If possible, you can add another additional context like how this feature can be implemented technically, what other alternative solutions we can have, etc.
## Open a PR for Bugfix or Feature
### Local Development with Examples
To 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.
First of all, build SWR assets
```sh
corepack enable
corepack pnpm install
pnpm watch
```
Install dependency of the target example, for instance `examples/basic`:
```sh
# by default it will run next dev for the example
pnpm next dev examples/basic
```
All examples are built with Next.js, so Next.js commands are all supported:
```sh
# if you want to build and start
pnpm next build examples/basic
pnpm next start examples/basic
```
## Update Documentation
To update the [SWR Documentation](https://swr.vercel.app), you can contribute to the [website repository](https://github.com/vercel/swr-site).
================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: Bug report
about: Create a bug report for the SWR library
---
# Bug report
## Description / Observed Behavior
What kind of issues did you encounter with SWR?
## Expected Behavior
How did you expect SWR to behave here?
## Repro Steps / Code Example
Or share your code snippet or a [CodeSandbox](https://codesandbox.io) link is also appreciated!
## Additional Context
SWR version.
Add any other context about the problem here.
================================================
FILE: .github/ISSUE_TEMPLATE/config.yml
================================================
blank_issues_enabled: false
contact_links:
- name: Ask Question
url: https://github.com/vercel/swr/discussions
about: Ask questions and discuss with other community members
================================================
FILE: .github/SECURITY.md
================================================
# Reporting Security Issues
If you believe you have found a security vulnerability in SWR, we encourage you to let us know right away.
We will investigate all legitimate reports and do our best to quickly fix the problem.
Email `security@vercel.com` to disclose any security vulnerabilities.
https://vercel.com/security
================================================
FILE: .github/workflows/install/action.yml
================================================
name: 'Install'
description: 'Set up and install dependencies'
runs:
using: composite
steps:
- name: Setup pnpm
uses: pnpm/action-setup@v4
- name: Lock Corepack version
shell: bash
run: pnpm i -g corepack@0.31.0
- name: Use Node.js lts
uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
registry-url: 'https://registry.npmjs.org'
- name: Install Dependencies
shell: bash
run: |
corepack enable
node -v
pnpm -v
pnpm install
================================================
FILE: .github/workflows/test-canary.yml
================================================
name: Test React Canary
on:
workflow_dispatch:
schedule:
- cron: '0 0 * * *'
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install
uses: ./.github/workflows/install
- name: Install Canary
run: corepack pnpm upgrade react@canary react-dom@canary use-sync-external-store@canary
- name: Lint and test
env:
TEST_REACT_CANARY: 1
run: |
pnpm clean
pnpm build
pnpm test
pnpm test:build
pnpm test-typing
================================================
FILE: .github/workflows/test-legacy-react.yml
================================================
name: Test React 17
on:
push:
branches:
- main
tags:
- v*
pull_request:
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install
uses: ./.github/workflows/install
- name: Test
env:
TEST_REACT_LEGACY: 1
run: |
pnpm clean
pnpm build
pnpm test
pnpm test:build
pnpm test-typing
================================================
FILE: .github/workflows/test-release.yml
================================================
name: Test and Release
on:
push:
branches:
- main
tags:
- v*
pull_request:
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install
uses: ./.github/workflows/install
- name: Lint and test
run: |
pnpm clean
pnpm build
pnpm run-all-checks
npm pack
pnpm attw
pnpm test
pnpm test:build
pnpm test-typing
e2e:
runs-on: ubuntu-latest
container:
image: mcr.microsoft.com/playwright:v1.57.0-noble
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install
uses: ./.github/workflows/install
- name: E2E Tests
run: |
pnpm clean
pnpm build
pnpm build:e2e
pnpm test:e2e
- name: Upload test results
if: always()
uses: actions/upload-artifact@v4
with:
name: playwright-report
path: playwright-report
release:
runs-on: ubuntu-latest
needs: ["test", "e2e"]
if: startsWith(github.ref, 'refs/tags/v')
permissions:
id-token: write
steps:
- name: Check out
uses: actions/checkout@v4
- name: Install
uses: ./.github/workflows/install
- name: Determine tag
id: determine_tag
run: |
echo "tag=$(echo $GITHUB_REF | grep -Eo 'alpha|beta|canary|rc')" >> $GITHUB_OUTPUT
- name: Publish to versioned tag
if: steps.determine_tag.outputs.tag != ''
run: |
echo "Publishing to ${{ steps.determine_tag.outputs.tag }} tag"
npm publish --access public --no-git-checks --provenance --tag ${{ steps.determine_tag.outputs.tag }}
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN_ELEVATED }}
- name: Publish to latest
if: steps.determine_tag.outputs.tag == ''
run: |
echo "Publishing to latest"
npm publish --access public --no-git-checks --provenance
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN_ELEVATED }}
================================================
FILE: .github/workflows/trigger-release.yml
================================================
on:
workflow_dispatch:
inputs:
releaseType:
description: Release stable or beta?
required: true
type: choice
options:
- beta
- stable
semverType:
description: semver type?
type: choice
options:
- patch
- minor
- major
name: Trigger Release
env:
SEMVER_TYPE: ${{ github.event.inputs.semverType }}
RELEASE_TYPE: ${{ github.event.inputs.releaseType }}
jobs:
start:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 10
token: ${{ secrets.RELEASE_BOT_GITHUB_TOKEN }}
- name: Install
uses: ./.github/workflows/install
- name: Test
run: |
pnpm clean
pnpm build
pnpm run-all-checks
pnpm test:build
- name: Configure git
run: |
git config user.name "vercel-release-bot"
git config user.email "infra+release@vercel.com"
- name: Bump version and tag
run: |
node ./scripts/bump-next-version.js
- name: Git push
run: |
git push origin main
git push origin --tags
================================================
FILE: .gitignore
================================================
node_modules
dist
*.log
*.tgz
.env
.next
.DS_Store
.idea
.vscode
.eslintcache
examples/**/yarn.lock
package-lock.json
*.tsbuildinfo
coverage
.rollup.cache
playwright-report
test-results
================================================
FILE: .husky/pre-commit
================================================
pnpm lint-staged && pnpm types:check
================================================
FILE: .npmrc
================================================
# prevent sub-packages from installing peer-deps (multiple react versions)
auto-install-peers=false
================================================
FILE: .swcrc
================================================
{
"$schema": "https://json.schemastore.org/swcrc",
"sourceMaps": true,
"jsc": {
"parser": {
"syntax": "typescript",
"tsx": true
},
"transform": {
"react": {
"runtime": "automatic"
}
}
}
}
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2023 Vercel, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
[](https://swr.vercel.app)
## Introduction
SWR is a React Hooks library for data fetching.
The name “**SWR**” is derived from `stale-while-revalidate`, a cache invalidation strategy popularized by [HTTP RFC 5861](https://tools.ietf.org/html/rfc5861).
**SWR** first returns the data from cache (stale), then sends the request (revalidate), and finally comes with the up-to-date data again.
With 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:
- **Fast**, **lightweight** and **reusable** data fetching
- Transport and protocol agnostic
- Built-in **cache** and request deduplication
- **Real-time** experience
- Revalidation on focus
- Revalidation on network recovery
- Polling
- Pagination and scroll position recovery
- SSR and SSG
- Local mutation (Optimistic UI)
- Built-in smart error retry
- TypeScript
- React Suspense
- React Native
...and a lot more.
With SWR, components will get **a stream of data updates constantly and automatically**. Thus, the UI will be always **fast** and **reactive**.
---
**View full documentation and examples on [swr.vercel.app](https://swr.vercel.app).**
## Quick Start
```js
import useSWR from 'swr'
function Profile() {
const { data, error, isLoading } = useSWR('/api/user', fetcher)
if (error) return
failed to load
if (isLoading) return
loading...
return
hello {data.name}!
}
```
In this example, the React Hook `useSWR` accepts a `key` and a `fetcher` function.
The `key` is a unique identifier of the request, normally the URL of the API. And the `fetcher` accepts
`key` as its parameter and returns the data asynchronously.
`useSWR` also returns 3 values: `data`, `isLoading` and `error`. When the request (fetcher) is not yet finished,
`data` will be `undefined` and `isLoading` will be `true`. When we get a response, it sets `data` and `error` based on the result
of `fetcher`, `isLoading` to false and rerenders the component.
Note that `fetcher` can be any asynchronous function, you can use your favourite data-fetching
library to handle that part.
---
**View full documentation and examples on [swr.vercel.app](https://swr.vercel.app).**
## Authors
This library is created by the team behind [Next.js](https://nextjs.org), with contributions from our community:
- Shu Ding ([@shuding\_](https://x.com/shuding_)) - [Vercel](https://vercel.com)
- Jiachi Liu ([@huozhi](https://x.com/huozhi)) - [Vercel](https://vercel.com)
- Guillermo Rauch ([@rauchg](https://x.com/rauchg)) - [Vercel](https://vercel.com)
- Joe Haddad ([@timer150](https://x.com/timer150)) - [Vercel](https://vercel.com)
- Paco Coursey ([@pacocoursey](https://x.com/pacocoursey)) - [Vercel](https://vercel.com)
[Contributors](https://github.com/vercel/swr/graphs/contributors)
Thanks to Ryan Chen for providing the awesome `swr` npm package name!
## License
The MIT License.
================================================
FILE: _internal/package.json
================================================
{
"main": "../dist/_internal/index.js",
"module": "../dist/_internal/index.mjs",
"types": "../dist/_internal/index.d.ts",
"private": true
}
================================================
FILE: e2e/site/README.md
================================================
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).
## Getting Started
First, run the development server:
```bash
npm run dev
# or
yarn dev
# or
pnpm dev
```
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
[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`.
The `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.
This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
## Learn More
To learn more about Next.js, take a look at the following resources:
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
## Deploy on Vercel
The 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.
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
================================================
FILE: e2e/site/app/basic-ssr/block.tsx
================================================
'use client'
import useSWR from 'swr'
import { useDebugHistory } from '~/lib/use-debug-history'
export default function Block() {
const { data } = useSWR('/api/data', async (url: string) => {
const res = await fetch(url).then(v => v.json())
return res.name
})
const debugRef = useDebugHistory(data, 'history:')
return (
<>
}
/**
* This renders 10,000 divs and is used to compare against the render performance
* when using swr.
*/
const CheapComponent = () => {
const cheapComponents = Array.from({ length: elementCount }, (_, i) => (
{i}
))
return (
Cheap Component
{cheapComponents}
)
}
/**
* This renders 10,000 divs, each of which uses the same swr hook.
*/
const ExpensiveComponent = () => {
const hookComponents = Array.from({ length: elementCount }, (_, i) => (
))
return (
>
)
}
================================================
FILE: e2e/site/app/render-count/page.tsx
================================================
'use client'
import useSWR from 'swr'
export default function Page() {
useSWR('swr should not cause extra rerenders')
console.count('render')
return
)
}
================================================
FILE: examples/axios/README.md
================================================
# Axios
## One-Click Deploy
Deploy your own SWR project with Vercel.
[](https://vercel.com/new/clone?s=https://github.com/vercel/swr/tree/main/examples/axios)
## How to Use
Download the example:
```bash
curl https://codeload.github.com/vercel/swr/tar.gz/main | tar -xz --strip=2 swr-main/examples/axios
cd axios
```
Install it and run:
```bash
yarn
yarn dev
# or
npm install
npm run dev
```
## The Idea behind the Example
Show a basic usage of SWR fetching using axios and a request object.
================================================
FILE: examples/axios/libs/useRequest.js
================================================
import useSWR from 'swr'
import axios from 'axios'
export default function useRequest(request, { fallbackData, ...config } = {}) {
return useSWR(
request,
() => axios(request || {}).then(response => response.data),
{
...config,
fallbackData: fallbackData && {
status: 200,
statusText: 'InitialData',
headers: {},
data: fallbackData
}
}
)
}
================================================
FILE: examples/axios/package.json
================================================
{
"name": "basic",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"dependencies": {
"axios": "0.27.2",
"next": "latest",
"react": "latest",
"react-dom": "latest",
"swr": "latest"
},
"scripts": {
"dev": "next",
"start": "next start",
"build": "next build"
}
}
================================================
FILE: examples/axios/pages/[user]/[repo].js
================================================
import Link from 'next/link'
import useRequest from '../../libs/useRequest'
export default function Repo() {
const id =
typeof window !== 'undefined' ? window.location.pathname.slice(1) : ''
const { data } = useRequest(
id
? {
url: '/api/data',
params: {
id
}
}
: null
)
return (
{id}
{data ? (
forks: {data.forks_count}
stars: {data.stargazers_count}
watchers: {data.watchers}
) : (
'loading...'
)}
Back
)
}
================================================
FILE: examples/axios/pages/api/data.js
================================================
import axios from 'axios'
const projects = [
'facebook/flipper', 'vuejs/vuepress', 'rust-lang/rust', 'vercel/next.js'
]
export default function api(req, res) {
if (req.query.id) {
// a slow endpoint for getting repo data
axios(`https://api.github.com/repos/${req.query.id}`)
.then(resp => resp.data)
.then(data => {
setTimeout(() => {
res.json(data)
}, 2000)
})
return
}
setTimeout(() => {
res.json(projects)
}, 2000)
}
================================================
FILE: examples/axios/pages/index.js
================================================
import Link from 'next/link'
import useRequest from '../libs/useRequest'
export default function Index() {
const { data } = useRequest({
url: '/api/data'
})
return (
Trending Projects
{data
? data.map(project => (
{project}
))
: 'loading...'}
)
}
================================================
FILE: examples/axios-typescript/README.md
================================================
# Axios TypeScript
## One-Click Deploy
Deploy your own SWR project with Vercel.
[](https://vercel.com/new/clone?s=https://github.com/vercel/swr/tree/main/examples/axios-typescript)
## How to Use
Download the example:
```bash
curl https://codeload.github.com/vercel/swr/tar.gz/main | tar -xz --strip=2 swr-main/examples/axios-typescript
cd axios-typescript
```
Install it and run:
```bash
yarn
yarn dev
# or
npm install
npm run dev
```
## The Idea behind the Example
Show how to use the basic axios along with TypeScript to type both the request object and the data received from SWR.
================================================
FILE: examples/axios-typescript/libs/useRequest.ts
================================================
import useSWR, { SWRConfiguration, SWRResponse } from 'swr'
import axios, { AxiosRequestConfig, AxiosResponse, AxiosError } from 'axios'
export type GetRequest = AxiosRequestConfig | null
interface Return
extends Pick<
SWRResponse, AxiosError>,
'isValidating' | 'error' | 'mutate'
> {
data: Data | undefined
response: AxiosResponse | undefined
}
export interface Config
extends Omit<
SWRConfiguration, AxiosError>,
'fallbackData'
> {
fallbackData?: Data
}
export default function useRequest(
request: GetRequest,
{ fallbackData, ...config }: Config = {}
): Return {
const {
data: response,
error,
isValidating,
mutate
} = useSWR, AxiosError>(
request,
/**
* NOTE: Typescript thinks `request` can be `null` here, but the fetcher
* function is actually only called by `useSWR` when it isn't.
*/
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
() => axios.request(request!),
{
...config,
fallbackData: fallbackData && {
status: 200,
statusText: 'InitialData',
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
config: request!,
headers: {},
data: fallbackData
} as AxiosResponse
}
)
return {
data: response && response.data,
response,
error,
isValidating,
mutate
}
}
================================================
FILE: examples/axios-typescript/next-env.d.ts
================================================
///
///
///
// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.
================================================
FILE: examples/axios-typescript/package.json
================================================
{
"name": "basic-typescript",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"dependencies": {
"axios": "0.23.0",
"next": "latest",
"react": "latest",
"react-dom": "latest",
"swr": "latest"
},
"scripts": {
"dev": "next",
"start": "next start",
"build": "next build"
},
"devDependencies": {
"@types/node": "16.7.2",
"@types/react": "17.0.19",
"typescript": "4.3.5"
}
}
================================================
FILE: examples/axios-typescript/pages/[user]/[repo].tsx
================================================
import Link from 'next/link'
import useRequest from '../../libs/useRequest'
export default function Repo() {
const id =
typeof window !== 'undefined' ? window.location.pathname.slice(1) : ''
const { data } = useRequest<{
forks_count: number
stargazers_count: number
watchers: number
}>({
url: '/api/data',
params: { id }
})
return (
{id}
{data ? (
forks: {data.forks_count}
stars: {data.stargazers_count}
watchers: {data.watchers}
) : (
'loading...'
)}
Back
)
}
================================================
FILE: examples/axios-typescript/pages/api/data.ts
================================================
import { NextApiRequest, NextApiResponse } from 'next'
import axios from 'axios'
const projects = [
'facebook/flipper',
'vuejs/vuepress',
'rust-lang/rust',
'vercel/next.js'
]
export default function api(req: NextApiRequest, res: NextApiResponse) {
if (req.query.id) {
// a slow endpoint for getting repo data
axios(`https://api.github.com/repos/${req.query.id}`)
.then(response => response.data)
.then(data => {
setTimeout(() => {
res.json(data)
}, 2000)
})
return
}
setTimeout(() => {
res.json(projects)
}, 2000)
}
================================================
FILE: examples/axios-typescript/pages/index.tsx
================================================
import Link from 'next/link'
import useRequest from '../libs/useRequest'
export default function Index() {
const { data } = useRequest({
url: '/api/data'
})
return (
Trending Projects
{data
? data.map(project => (
{project}
))
: 'loading...'}
)
}
================================================
FILE: examples/axios-typescript/tsconfig.json
================================================
{
"compilerOptions": {
"target": "es5",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve"
},
"exclude": [
"node_modules"
],
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx"
]
}
================================================
FILE: examples/basic/README.md
================================================
# Basic
## One-Click Deploy
Deploy your own SWR project with Vercel.
[](https://vercel.com/new/clone?s=https://github.com/vercel/swr/tree/main/examples/basic)
## How to Use
Download the example:
```bash
curl https://codeload.github.com/vercel/swr/tar.gz/main | tar -xz --strip=2 swr-main/examples/basic
cd basic
```
Install it and run:
```bash
yarn
yarn dev
# or
npm install
npm run dev
```
## The Idea behind the Example
Show a basic usage of SWR fetching data from an API in two different pages.
================================================
FILE: examples/basic/libs/fetch.js
================================================
export default async function fetcher(...args) {
const res = await fetch(...args)
return res.json()
}
================================================
FILE: examples/basic/package.json
================================================
{
"name": "basic",
"private": true,
"license": "MIT",
"dependencies": {
"next": "latest",
"react": "latest",
"react-dom": "latest",
"swr": "latest"
},
"scripts": {
"dev": "next",
"start": "next start",
"build": "next build"
}
}
================================================
FILE: examples/basic/pages/[user]/[repo].js
================================================
import Link from 'next/link'
import fetch from '../../libs/fetch'
import useSWR from 'swr'
export default function Repo() {
const id = typeof window !== 'undefined' ? window.location.pathname.slice(1) : ''
const { data } = useSWR('/api/data?id=' + id, fetch)
return (
{id}
{
data ?
forks: {data.forks_count}
stars: {data.stargazers_count}
watchers: {data.watchers}
: 'loading...'
}
Back
)
}
================================================
FILE: examples/basic/pages/api/data.js
================================================
const projects = [
'facebook/flipper', 'vuejs/vuepress', 'rust-lang/rust', 'vercel/next.js'
]
export default function api(req, res) {
if (req.query.id) {
// a slow endpoint for getting repo data
fetch(`https://api.github.com/repos/${req.query.id}`)
.then(resp => resp.json())
.then(data => {
setTimeout(() => {
res.json(data)
}, 2000)
})
return
}
setTimeout(() => {
res.json(projects)
}, 2000)
}
================================================
FILE: examples/basic/pages/index.js
================================================
import Link from 'next/link'
import fetch from '../libs/fetch'
import useSWR from 'swr'
export default function Index() {
const { data } = useSWR('/api/data', fetch)
return (
Trending Projects
{
data ? data.map(project =>
{project}
) : 'loading...'
}
)
}
================================================
FILE: examples/basic-typescript/README.md
================================================
# Basic TypeScript
## One-Click Deploy
Deploy your own SWR project with Vercel.
[](https://vercel.com/new/clone?s=https://github.com/vercel/swr/tree/main/examples/basic-typescript)
## How to Use
Download the example:
```bash
curl https://codeload.github.com/vercel/swr/tar.gz/main | tar -xz --strip=2 swr-main/examples/basic-typescript
cd basic-typescript
```
Install it and run:
```bash
yarn
yarn dev
# or
npm install
npm run dev
```
## The Idea behind the Example
Show how to use the basic example along with TypeScript to type the data received from SWR.
================================================
FILE: examples/basic-typescript/libs/fetch.ts
================================================
export default async function fetcher(
input: RequestInfo,
init?: RequestInit
): Promise {
const res = await fetch(input, init)
return res.json()
}
================================================
FILE: examples/basic-typescript/next-env.d.ts
================================================
///
///
///
// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.
================================================
FILE: examples/basic-typescript/package.json
================================================
{
"name": "basic-typescript",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"dependencies": {
"next": "latest",
"react": "latest",
"react-dom": "latest",
"swr": "latest"
},
"scripts": {
"dev": "next",
"start": "next start",
"build": "next build"
},
"devDependencies": {
"@types/node": "16.7.2",
"@types/react": "17.0.19",
"typescript": "4.3.5"
}
}
================================================
FILE: examples/basic-typescript/pages/[user]/[repo].tsx
================================================
import Link from 'next/link'
import fetch from '../../libs/fetch'
import useSWR from 'swr'
export default function Repo() {
const id =
typeof window !== 'undefined' ? window.location.pathname.slice(1) : ''
const { data } = useSWR<{
forks_count: number
stargazers_count: number
watchers: number
}>('/api/data?id=' + id, fetch)
return (
{id}
{data ? (
forks: {data.forks_count}
stars: {data.stargazers_count}
watchers: {data.watchers}
) : (
'loading...'
)}
Back
)
}
================================================
FILE: examples/basic-typescript/pages/api/data.ts
================================================
import { NextApiRequest, NextApiResponse } from 'next'
const projects = [
'facebook/flipper',
'vuejs/vuepress',
'rust-lang/rust',
'vercel/next.js'
]
export default function api(req: NextApiRequest, res: NextApiResponse) {
if (req.query.id) {
// a slow endpoint for getting repo data
fetch(`https://api.github.com/repos/${req.query.id}`)
.then(resp => resp.json())
.then(data => {
setTimeout(() => {
res.json(data)
}, 2000)
})
return
}
setTimeout(() => {
res.json(projects)
}, 2000)
}
================================================
FILE: examples/basic-typescript/pages/index.tsx
================================================
import Link from 'next/link'
import fetch from '../libs/fetch'
import useSWR from 'swr'
export default function HomePage() {
const { data } = useSWR('/api/data', fetch)
const { data: data2 } = useSWR(null, fetch)
return (
Trending Projects
{data2}
{data
? data.map(project => (
{project}
))
: 'loading...'}
)
}
================================================
FILE: examples/basic-typescript/tsconfig.json
================================================
{
"compilerOptions": {
"target": "es5",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve"
},
"exclude": [
"node_modules"
],
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx"
]
}
================================================
FILE: examples/focus-revalidate/README.md
================================================
# Focus Revalidate
## One-Click Deploy
Deploy your own SWR project with Vercel.
[](https://vercel.com/new/clone?s=https://github.com/vercel/swr/tree/main/examples/focus-revalidate)
## How to Use
Download the example:
```bash
curl https://codeload.github.com/vercel/swr/tar.gz/main | tar -xz --strip=2 swr-main/examples/focus-revalidate
cd focus-revalidate
```
Install it and run:
```bash
yarn
yarn dev
# or
npm install
npm run dev
```
## The Idea behind the Example
Basic authentication example showing how the revalidate on focus feature works and to trigger a revalidation on a per-hook call basis.
================================================
FILE: examples/focus-revalidate/components/button.js
================================================
export default function Button({ children, ...props }) {
return
}
================================================
FILE: examples/focus-revalidate/libs/auth.js
================================================
// mock login and logout
export function login() {
document.cookie = 'swr-test-token=swr;'
}
export function logout() {
document.cookie = 'swr-test-token=; expires=Thu, 01 Jan 1970 00:00:01 GMT;'
}
================================================
FILE: examples/focus-revalidate/libs/fetch.js
================================================
export default async function fetcher(...args) {
const res = await fetch(...args)
return res.json()
}
================================================
FILE: examples/focus-revalidate/package.json
================================================
{
"name": "focus-revalidate",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"dependencies": {
"next": "latest",
"react": "latest",
"react-dom": "latest",
"swr": "latest"
},
"scripts": {
"dev": "next",
"start": "next start",
"build": "next build"
}
}
================================================
FILE: examples/focus-revalidate/pages/api/user.js
================================================
// an endpoint for getting user info
export default function user(req, res) {
if (req.cookies['swr-test-token'] === 'swr') {
// authorized
res.json({
loggedIn: true,
name: 'Shu',
avatar: 'https://github.com/shuding.png'
})
return
}
res.json({
loggedIn: false
})
}
================================================
FILE: examples/focus-revalidate/pages/index.js
================================================
import Button from '../components/button'
import fetch from '../libs/fetch'
import { login, logout } from '../libs/auth'
import useSWR from 'swr'
export default function Index() {
const { data, mutate } = useSWR('/api/user', fetch)
if (!data) return
loading...
if (data.loggedIn) {
return
Welcome, {data.name}
} else {
return
Please login
}
}
================================================
FILE: examples/global-fetcher/README.md
================================================
# Global Fetcher
## One-Click Deploy
Deploy your own SWR project with Vercel.
[](https://vercel.com/new/clone?s=https://github.com/vercel/swr/tree/main/examples/global-fetcher)
## How to Use
Download the example:
```bash
curl https://codeload.github.com/vercel/swr/tar.gz/main | tar -xz --strip=2 swr-main/examples/global-fetcher
cd global-fetcher
```
Install it and run:
```bash
yarn
yarn dev
# or
npm install
npm run dev
```
## The Idea behind the Example
Use the `SWRConfig` provider to set up the fetcher globally instead of a per-hook call.
================================================
FILE: examples/global-fetcher/libs/fetch.js
================================================
export default async function fetcher(...args) {
const res = await fetch(...args)
return res.json()
}
================================================
FILE: examples/global-fetcher/package.json
================================================
{
"name": "global-fetcher",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"dependencies": {
"next": "latest",
"react": "latest",
"react-dom": "latest",
"swr": "latest"
},
"scripts": {
"dev": "next",
"start": "next start",
"build": "next build"
}
}
================================================
FILE: examples/global-fetcher/pages/[user]/[repo].js
================================================
import Link from 'next/link'
import useSWR from 'swr'
export default function Repo() {
const id = typeof window !== 'undefined' ? window.location.pathname.slice(1) : ''
const { data } = useSWR('/api/data?id=' + id)
return (
{id}
{
data ?
forks: {data.forks_count}
stars: {data.stargazers_count}
watchers: {data.watchers}
: 'loading...'
}
Back
)
}
================================================
FILE: examples/global-fetcher/pages/_app.js
================================================
import React from 'react'
import App from 'next/app'
import { SWRConfig } from 'swr'
import fetch from '../libs/fetch.js';
export default class MyApp extends App {
render() {
const { Component, pageProps } = this.props
return
}
}
================================================
FILE: examples/global-fetcher/pages/api/data.js
================================================
const projects = [
'facebook/flipper', 'vuejs/vuepress', 'rust-lang/rust', 'vercel/next.js'
]
export default function api(req, res) {
if (req.query.id) {
// a slow endpoint for getting repo data
fetch(`https://api.github.com/repos/${req.query.id}`)
.then(resp => resp.json())
.then(data => {
setTimeout(() => {
res.json(data)
}, 2000)
})
return
}
setTimeout(() => {
res.json(projects)
}, 2000)
}
================================================
FILE: examples/global-fetcher/pages/index.js
================================================
import Link from 'next/link'
import useSWR from 'swr'
export default function Index() {
const { data } = useSWR('/api/data')
return (
Trending Projects
{data
? data.map(project => (
{project}
))
: 'loading...'}
)
}
================================================
FILE: examples/infinite/README.md
================================================
# useSWRInfinite
## One-Click Deploy
Deploy your own SWR project with Vercel.
[](https://vercel.com/new/clone?s=https://github.com/vercel/swr/tree/main/examples/infinite)
## How to Use
Download the example:
```bash
curl https://codeload.github.com/vercel/swr/tar.gz/main | tar -xz --strip=2 swr-main/examples/infinite
cd basic
```
Install it and run:
```bash
yarn
yarn dev
# or
npm install
npm run dev
```
## The Idea behind the Example
Show usage of useSWRInfinite with load more data button
================================================
FILE: examples/infinite/libs/fetch.js
================================================
export default async function fetcher(...args) {
const res = await fetch(...args)
return res.json()
}
================================================
FILE: examples/infinite/package.json
================================================
{
"name": "infinite",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"dependencies": {
"next": "latest",
"react": "latest",
"react-dom": "latest",
"swr": "latest"
},
"scripts": {
"dev": "next",
"start": "next start",
"build": "next build"
}
}
================================================
FILE: examples/infinite/pages/index.js
================================================
import { useState } from 'react'
import useSWRInfinite from 'swr/infinite'
import fetch from '../libs/fetch'
const PAGE_SIZE = 6
export default function App() {
const [repo, setRepo] = useState('reactjs/react-a11y')
const [val, setVal] = useState(repo)
const { data, error, mutate, size, setSize, isValidating } = useSWRInfinite(
(index) =>
`https://api.github.com/repos/${repo}/issues?per_page=${PAGE_SIZE}&page=${
index + 1
}`,
fetch
)
const issues = data ? [].concat(...data) : []
const isLoadingInitialData = !data && !error
const isLoadingMore =
isLoadingInitialData ||
(size > 0 && data && typeof data[size - 1] === 'undefined')
const isEmpty = data?.[0]?.length === 0
const isReachingEnd =
isEmpty || (data && data[data.length - 1]?.length < PAGE_SIZE)
const isRefreshing = isValidating && data && data.length === size
return (
)
}
function Other() {
const { data } = useSWR("globalState", { fallbackData: initialStore })
if (!data) {
return null
}
return (
Another Component:
My name is {data.name}.
)
}
export default function Index() {
return (
useSWR can share state between components:
)
}
================================================
FILE: examples/optimistic-ui/README.md
================================================
# Optimistic UI
## One-Click Deploy
Deploy your own SWR project with Vercel.
[](https://vercel.com/new/clone?s=https://github.com/vercel/swr/tree/main/examples/optimistic-ui)
## How to Use
Download the example:
```bash
curl https://codeload.github.com/vercel/swr/tar.gz/main | tar -xz --strip=2 swr-main/examples/optimistic-ui
cd optimistic-ui
```
Install it and run:
```bash
yarn
yarn dev
# or
npm install
npm run dev
```
## The Idea behind the Example
Example 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.
================================================
FILE: examples/optimistic-ui/libs/fetch.js
================================================
export default async function fetcher(...args) {
const res = await fetch(...args)
if (!res.ok) throw new Error('Failed to fetch')
return res.json()
}
================================================
FILE: examples/optimistic-ui/package.json
================================================
{
"name": "optimistic-ui",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"dependencies": {
"next": "latest",
"react": "latest",
"react-dom": "latest",
"swr": "latest"
},
"scripts": {
"dev": "next",
"start": "next start",
"build": "next build"
}
}
================================================
FILE: examples/optimistic-ui/pages/_app.js
================================================
import '../styles.css'
export default function App({ Component, pageProps }) {
return
}
================================================
FILE: examples/optimistic-ui/pages/api/todos.js
================================================
let todos = []
const delay = () => new Promise(res => setTimeout(() => res(), 1000))
async function getTodos() {
await delay()
return todos.sort((a, b) => (a.text < b.text ? -1 : 1))
}
async function addTodo(todo) {
await delay()
// Sometimes it will fail, this will cause a regression on the UI
if (Math.random() < 0.2 || !todo.text)
throw new Error('Failed to add new item!')
todo.text = todo.text.charAt(0).toUpperCase() + todo.text.slice(1)
todos = [...todos, todo]
return todo
}
export default async function api(req, res) {
try {
if (req.method === 'POST') {
const body = JSON.parse(req.body)
return res.json(await addTodo(body))
}
return res.json(await getTodos())
} catch (err) {
return res.status(500).json({ error: err.message })
}
}
================================================
FILE: examples/optimistic-ui/pages/index.js
================================================
import useSWR from 'swr'
import React, { useState } from 'react'
import fetch from '../libs/fetch'
export default function App() {
const [text, setText] = useState('')
const { data, mutate } = useSWR('/api/todos', fetch)
const [state, setState] = useState()
return (
{/* */}
Optimistic UI with SWR
This application optimistically updates the data, while revalidating in
the background. The POST API auto capitializes the data,
and only returns the new added one instead of the full list. And the{' '}
GET API returns the full list in order.
})
) : (
No todos yet. Try adding lowercased "banana" and "apple" to the
list.
)
) : (
Loading...
)}
)
}
================================================
FILE: examples/optimistic-ui/styles.css
================================================
html {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen,
Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
text-align: center;
}
body {
max-width: 600px;
margin: auto;
}
h1 {
margin-top: 1em;
}
.note {
text-align: left;
font-size: 0.9em;
line-height: 1.5;
color: #666;
}
.note svg {
margin-right: 0.5em;
vertical-align: -2px;
width: 14px;
height: 14px;
margin-right: 5px;
}
form {
display: flex;
margin: 8px 0;
gap: 8px;
}
input {
flex: 1;
}
input,
button {
font-size: 16px;
padding: 6px 5px;
}
code {
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
Liberation Mono, Courier New, monospace;
font-feature-settings: 'rlig' 1, 'calt' 1, 'ss01' 1;
background-color: #eee;
padding: 1px 3px;
border-radius: 2px;
}
ul {
text-align: left;
list-style: none;
padding: 0;
}
li {
margin: 8px 0;
padding: 10px;
border-radius: 4px;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.12), 0 0 0 1px #ededed;
}
i {
color: #999;
}
.info,
.success,
.error {
display: block;
text-align: left;
padding: 6px 0;
font-size: 0.9em;
opacity: 0.9;
}
.info {
color: #666;
}
.success {
color: #4caf50;
}
.error {
color: #f44336;
}
================================================
FILE: examples/optimistic-ui-immer/README.md
================================================
# Optimistic UI with Immer
## One-Click Deploy
Deploy your own SWR project with Vercel.
[](https://vercel.com/new/clone?s=https://github.com/vercel/swr/tree/main/examples/optimistic-ui-immer)
## How to Use
Download the example:
```bash
curl https://codeload.github.com/vercel/swr/tar.gz/main | tar -xz --strip=2 swr-main/examples/optimistic-ui-immer
cd optimistic-ui-immer
```
Install it and run:
```bash
yarn
yarn dev
# or
npm install
npm run dev
```
## The Idea behind the Example
Example 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.
================================================
FILE: examples/optimistic-ui-immer/libs/fetch.js
================================================
export default async function fetcher(...args) {
const res = await fetch(...args)
return res.json()
}
================================================
FILE: examples/optimistic-ui-immer/package.json
================================================
{
"name": "optimistic-ui-immer",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"dependencies": {
"immer": "9.0.5",
"next": "latest",
"react": "latest",
"react-dom": "latest",
"swr": "latest"
},
"scripts": {
"dev": "next",
"start": "next start",
"build": "next build"
}
}
================================================
FILE: examples/optimistic-ui-immer/pages/api/data.js
================================================
const data = []
function shouldFail() {
return Math.random() > 0.8
}
export default function api(req, res) {
if (req.method === 'POST') {
const body = JSON.parse(req.body)
// sometimes it will fail, and this will cause a regression in the UI
if (!shouldFail()) {
data.push(body.text);
}
res.json(data)
return
}
setTimeout(() => {
res.json(data)
}, 2000)
}
================================================
FILE: examples/optimistic-ui-immer/pages/index.js
================================================
import React from 'react'
import fetch from '../libs/fetch'
import useSWR, { mutate } from 'swr'
import produce from "immer"
export default function Index() {
const [text, setText] = React.useState('');
const { data } = useSWR('/api/data', fetch)
async function handleSubmit(event) {
event.preventDefault()
// Call mutate to optimistically update the UI.
// We use Immer produce to allow us to perform an immutable change
// while coding it as a normal mutation of the same object.
mutate("/api/data", produce(draftData => {
draftData.push(text)
}), false)
// Then we send the request to the API and let mutate
// update the data with the API response.
// Our action may fail in the API function, and the response differ
// from what was optimistically updated, in that case, the UI will be
// changed to match the API response.
// The fetch could also fail, in that case, the UI will
// be in an incorrect state until the next successful fetch.
mutate('/api/data', await fetch('/api/data', {
method: 'POST',
body: JSON.stringify({ text })
}))
setText('')
}
return
{data ? data.map(datum =>
{datum}
) : 'loading...'}
}
================================================
FILE: examples/prefetch-preload/README.md
================================================
# Prefetch & Preload
## One-Click Deploy
Deploy your own SWR project with Vercel.
[](https://vercel.com/new/clone?s=https://github.com/vercel/swr/tree/main/examples/prefetch-preload)
## How to Use
Download the example:
```bash
curl https://codeload.github.com/vercel/swr/tar.gz/main | tar -xz --strip=2 swr-main/examples/prefetch-preload
cd prefetch-preload
```
Install it and run:
```bash
yarn
yarn dev
# or
npm install
npm run dev
```
## The Idea behind the Example
This example shows multiple ways to prefetch data to be used by SWR later.
- Use a `` to get the browser to load the data while rendering the HTML
- If in a browser, run the fetch + mutate outside the component
- After rendering use an effect in React to prefetch the next page's data
- When the user moves the mouse over a link trigger a fetch + mutate for the next page
In 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.
================================================
FILE: examples/prefetch-preload/libs/fetch.js
================================================
export default async function fetcher(...args) {
const res = await fetch(...args)
return res.json()
}
================================================
FILE: examples/prefetch-preload/package.json
================================================
{
"name": "prefetch-preload",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"dependencies": {
"next": "latest",
"react": "latest",
"react-dom": "latest",
"swr": "latest"
},
"scripts": {
"dev": "next",
"start": "next start",
"build": "next build"
}
}
================================================
FILE: examples/prefetch-preload/pages/[user]/[repo].js
================================================
import Head from "next/head"
import Link from 'next/link'
import React from 'react'
import fetch from '../../libs/fetch'
import useSWR, { mutate } from 'swr'
function prefetchParent() {
return fetch('/api/data')
.then(projects => mutate('/api/data', projects, false))
}
// if we are on the browser trigger a prefetch as soon as possible
if (typeof window !== 'undefined') prefetchParent()
export default function Repo() {
const id = typeof window !== 'undefined' ? window.location.pathname.slice(1) : ''
const { data } = useSWR('/api/data?id=' + id, fetch)
React.useEffect(() => {
prefetchParent()
}, [])
function handleMouseEnter() {
prefetchParent()
}
return (
<>
{/* This will tell the browser to preload the data for our page */}
{id && }
{id}
{
data ?
forks: {data.forks_count}
stars: {data.stargazers_count}
watchers: {data.watchers}
: 'loading...'
}
Back
>
)
}
================================================
FILE: examples/prefetch-preload/pages/api/data.js
================================================
const projects = [
'facebook/flipper', 'vuejs/vuepress', 'rust-lang/rust', 'vercel/next.js'
]
export default function api(req, res) {
if (req.query.id) {
// a slow endpoint for getting repo data
fetch(`https://api.github.com/repos/${req.query.id}`)
.then(resp => resp.json())
.then(data => {
setTimeout(() => {
res.json(data)
}, 2000)
})
return
}
setTimeout(() => {
res.json(projects)
}, 2000)
}
================================================
FILE: examples/prefetch-preload/pages/index.js
================================================
import React from 'react'
import Head from "next/head";
import Link from 'next/link'
import fetch from '../libs/fetch'
import useSWR, { mutate } from 'swr'
function prefetchData() {
return fetch('/api/data')
.then(data => {
mutate('/api/data', data, false)
return data
})
}
function prefetchItem(project) {
return fetch(`https://api.github.com/repos/${project}`).then(data => {
mutate(`/api/data?id=${project}`, data, false)
return data
})
}
function prefetchWithProjects() {
return prefetchData()
.then(projects => Promise.all(projects.map(prefetchItem)))
}
// if we are on the browser trigger a prefetch as soon as possible
if (typeof window !== 'undefined') prefetchWithProjects()
export default function Index() {
const { data } = useSWR('/api/data', fetch)
// This effect will fetch all projects after mounting
React.useEffect(() => {
if (!data) return
if (data.length === 0) return
data.forEach(prefetchItem)
}, [data]);
// With this handler, you could prefetch the data for a specific
// project the moment the user moves the mouse over the link
function handleMouseEnter(event) {
// In our case, we could get the ID from the href so we use that
prefetchItem(event.target.getAttribute("href").slice(1))
}
return (
<>
{/* This will tell the browser to preload the data for our page */}
Trending Projects
{
data ? data.map(project =>
{project}
) : 'loading...'
}
>
)
}
================================================
FILE: examples/refetch-interval/README.md
================================================
# Refetch Interval
## One-Click Deploy
Deploy your own SWR project with Vercel Now.
[](https://vercel.com/new/clone?s=https://github.com/vercel/swr/tree/main/examples/refetch-interval)
## How to Use
Download the example:
```bash
curl https://codeload.github.com/vercel/swr/tar.gz/main | tar -xz --strip=2 swr-main/examples/refetch-interval
cd refetch-interval
```
Install it and run:
```bash
yarn
yarn dev
# or
npm install
npm run dev
```
## The Idea behind the Example
Show how to make SWR fetch the API again in an interval automatically to ensure the data is up-to-date.
================================================
FILE: examples/refetch-interval/components/button.js
================================================
export default function Button({ children, ...props }) {
return
}
================================================
FILE: examples/refetch-interval/libs/fetch.js
================================================
export default async function fetcher(...args) {
const res = await fetch(...args)
return res.json()
}
================================================
FILE: examples/refetch-interval/package.json
================================================
{
"name": "refetch-interval",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"dependencies": {
"next": "latest",
"react": "latest",
"react-dom": "latest",
"swr": "latest"
},
"scripts": {
"dev": "next",
"start": "next start",
"build": "next build"
}
}
================================================
FILE: examples/refetch-interval/pages/api/data.js
================================================
// an simple endpoint for getting current list
let list = []
export default function api(req, res) {
if (req.query.add) {
list.push(req.query.add)
} else if (req.query.clear) {
list = []
}
res.json(list)
}
================================================
FILE: examples/refetch-interval/pages/index.js
================================================
import { useState } from 'react'
import Button from '../components/button'
import fetch from '../libs/fetch'
import useSWR from 'swr'
export default function Index() {
const { data, mutate } = useSWR('/api/data', fetch, {
// revalidate the data per second
refreshInterval: 1000
})
const [value, setValue] = useState('')
if (!data) return
loading...
return (
Refetch Interval (1s)
Todo List
{data.map(item =>
{item}
)}
)
}
================================================
FILE: examples/server-render/README.md
================================================
# Server Render
## One-Click Deploy
Deploy your own SWR project with Vercel.
[](https://vercel.com/new/clone?s=https://github.com/vercel/swr/tree/main/examples/server-render)
## How to Use
Download the example:
```bash
curl https://codeload.github.com/vercel/swr/tar.gz/main | tar -xz --strip=2 swr-main/examples/server-render
cd server-render
```
Install it and run:
```bash
yarn
yarn dev
# or
npm install
npm run dev
```
## The Idea behind the Example
This example shows how to combine Next.js getServerSideProps with the SWR `fallbackData` option to support Server-Side Rendering.
The 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.
================================================
FILE: examples/server-render/libs/fetcher.js
================================================
export default async function fetcher(...args) {
const res = await fetch(...args)
return res.json()
}
================================================
FILE: examples/server-render/package.json
================================================
{
"name": "server-render",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"dependencies": {
"next": "latest",
"react": "latest",
"react-dom": "latest",
"swr": "latest"
},
"scripts": {
"dev": "next",
"start": "next start",
"build": "next build"
}
}
================================================
FILE: examples/server-render/pages/[pokemon].js
================================================
import Link from 'next/link'
import fetcher from '../libs/fetcher'
import useSWR from 'swr'
const getURL = pokemon => `https://pokeapi.co/api/v2/pokemon/${pokemon}`
export default function Pokemon({ pokemon, fallbackData }) {
const { data } = useSWR(getURL(pokemon), fetcher, { fallbackData })
return (
{pokemon}
{data ? (
height: {data.height}
weight: {data.weight}
{data.types.map(({ type }) => (
{type.name}
))}
) : (
'loading...'
)}
Back
)
}
export async function getServerSideProps({ query }) {
const data = await fetcher(getURL(query.pokemon))
return { props: { fallbackData: data, pokemon: query.pokemon } }
}
================================================
FILE: examples/server-render/pages/index.js
================================================
import Link from 'next/link'
import fetcher from '../libs/fetcher'
import useSWR from 'swr'
const URL = 'https://pokeapi.co/api/v2/pokemon/'
export default function Home({ fallbackData }) {
const { data } = useSWR(URL, fetcher, { fallbackData })
return (
)
}
export async function getServerSideProps() {
const data = await fetcher(URL)
return { props: { fallbackData: data } }
}
================================================
FILE: examples/storage-tab-sync/README.md
================================================
# Storage Tab Sync
## One-Click Deploy
Deploy your own SWR project with Vercel.
[](https://vercel.com/new/clone?s=https://github.com/vercel/swr/tree/main/examples/storage-tab-sync)
## How to Use
Download the example:
```bash
curl https://codeload.github.com/vercel/swr/tar.gz/main | tar -xz --strip=2 swr-main/examples/storage-tab-sync
cd storage-tab-sync
```
Install it and run:
```bash
yarn
yarn dev
# or
npm install
npm run dev
```
## The Idea behind the Example
Show how you could use SWR to synchronize localStorage values between tabs.
================================================
FILE: examples/storage-tab-sync/libs/storage.js
================================================
export default async function storage(key) {
const value = localStorage.getItem(key)
if (!value) return undefined
return JSON.parse(value)
}
================================================
FILE: examples/storage-tab-sync/package.json
================================================
{
"name": "storage-tab-sync",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"dependencies": {
"next": "latest",
"react": "latest",
"react-dom": "latest",
"swr": "latest"
},
"scripts": {
"dev": "next",
"start": "next start",
"build": "next build"
}
}
================================================
FILE: examples/storage-tab-sync/pages/index.js
================================================
import storage from '../libs/storage'
import useSWR, { mutate } from 'swr'
export default function Index() {
const { data = { name: "" } } = useSWR('user-name', storage)
function handleChange(event) {
localStorage.setItem(
'user-name',
JSON.stringify({ name: event.target.value })
)
mutate('user-name', { name: event.target.value })
}
return
}
================================================
FILE: examples/subscription/README.md
================================================
# Subscription
## One-Click Deploy
Deploy your own SWR project with Vercel.
[](https://vercel.com/new/clone?s=https://github.com/vercel/swr/tree/main/examples/subscription)
## How to Use
Download the example:
```bash
curl https://codeload.github.com/vercel/swr/tar.gz/main | tar -xz --strip=2 swr-main/examples/subscription
cd subscription
```
Install it and run:
```bash
yarn
yarn dev
# or
npm install
npm run dev
```
## The Idea behind the Example
Show how you could use SWR to subscribe async observable data into your app.
================================================
FILE: examples/subscription/package.json
================================================
{
"name": "subscription",
"private": true,
"license": "MIT",
"dependencies": {
"next": "latest",
"react": "latest",
"react-dom": "latest",
"swr": "latest"
},
"scripts": {
"dev": "next",
"start": "next start",
"build": "next build"
}
}
================================================
FILE: examples/subscription/pages/index.js
================================================
import React from "react"
import useSWRSubscription from "swr/subscription"
import EventEmitter from "events"
const event = new EventEmitter()
// Simulating an external data source.
let stopped = false
async function start () {
for (let i = 0; i < 100; i++) {
await new Promise(res => setTimeout(res, 1000))
if (stopped) return
if (i % 3 === 0 && i !== 0) {
event.emit("error", new Error("error: " + i));
} else {
event.emit("data", "state: " + i);
}
}
}
export default function page() {
const { data, error } = useSWRSubscription('my-sub', (key, { next }) => {
event.on("data", (value) => next(undefined, value));
event.on("error", (err) => next(err));
start();
return () => {
stopped = true;
};
})
return (
SWR Subscription
Received every second, error when data is times of 3
{data}
{error ? error.message : ""}
)
}
================================================
FILE: examples/suspense/README.md
================================================
# Basic
## One-Click Deploy
Deploy your own SWR project with Vercel.
[](https://vercel.com/new/clone?s=https://github.com/vercel/swr/tree/main/examples/suspense)
## How to Use
Download the example:
```bash
curl https://codeload.github.com/vercel/swr/tar.gz/main | tar -xz --strip=2 swr-main/examples/suspense
cd suspense
```
Install it and run:
```bash
yarn
yarn dev
# or
npm install
npm run dev
```
## The Idea behind the Example
Show how to use the SWR suspense option with React suspense.
================================================
FILE: examples/suspense/app/layout.jsx
================================================
export default function RootLayout({
children
}) {
return (
{children}
)
}
================================================
FILE: examples/suspense/app/rsc/[user]/[repo]/error.jsx
================================================
'use client'
export default function ErrorPage() {
return
}
preloadRemote()
function Fallback({ resetErrorBoundary }: any) {
return (
Something went wrong:
)
}
function RemoteData() {
return (
{
preloadRemote()
}}
>
loading
}>
)
}
export default RemoteData
================================================
FILE: examples/suspense-retry/app/page.tsx
================================================
import { Suspense } from 'react'
import dynamic from 'next/dynamic'
const RemoteData = dynamic(() => import('./manual-retry'), {
ssr: false
})
export default function HomePage() {
return (
loading component}>
)
}
================================================
FILE: examples/suspense-retry/app/use-remote-data.ts
================================================
'use client'
import useSWR from 'swr'
import { preload } from 'swr'
let count = 0
const fetcher = () => {
count++
if (count === 1) return Promise.reject('wrong')
return fetch('/api')
.then(r => r.json())
.then(r => r.data)
}
const key = 'manual-retry'
export const useRemoteData = () =>
useSWR(key, fetcher, {
suspense: true
})
export const preloadRemote = () => preload(key, fetcher)
================================================
FILE: examples/suspense-retry/next-env.d.ts
================================================
///
///
///
// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.
================================================
FILE: examples/suspense-retry/next.config.js
================================================
/** @type {import('next').NextConfig} */
const nextConfig = {
experimental: {
serverActions: true,
},
}
module.exports = nextConfig
================================================
FILE: examples/suspense-retry/package.json
================================================
{
"name": "site",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"@types/node": "^20.2.5",
"@types/react": "^18.2.8",
"@types/react-dom": "18.2.4",
"next": "^latest",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"typescript": "5.1.3",
"swr": "*"
}
}
================================================
FILE: examples/suspense-retry/pages/retry.tsx
================================================
import { Suspense } from 'react'
import dynamic from 'next/dynamic'
const RemoteData = dynamic(() => import('../app/manual-retry'), {
ssr: false
})
export default function HomePage() {
return (
loading component}>
)
}
================================================
FILE: examples/suspense-retry/tsconfig.json
================================================
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"baseUrl": ".",
"paths": {
"~/*": ["./*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}
================================================
FILE: immutable/package.json
================================================
{
"main": "../dist/immutable/index.js",
"module": "../dist/immutable/index.mjs",
"types": "../dist/immutable/index.d.ts",
"private": true
}
================================================
FILE: infinite/package.json
================================================
{
"main": "../dist/infinite/index.js",
"module": "../dist/infinite/index.mjs",
"types": "../dist/infinite/index.d.ts",
"private": true
}
================================================
FILE: jest.config.build.js
================================================
import config from './jest.config.js'
const useBuildConfig = {
...config,
// override to use build files
moduleNameMapper: {}
}
export default useBuildConfig
================================================
FILE: jest.config.js
================================================
const config = {
testEnvironment: 'jsdom',
testRegex: '/test/.*\\.test\\.tsx?$',
testPathIgnorePatterns: ['/node_modules/', '/e2e/'],
modulePathIgnorePatterns: ['/examples/'],
setupFilesAfterEnv: ['/test/jest-setup.ts'],
moduleNameMapper: {
'^swr$': '/src/index/index.ts',
'^swr/infinite$': '/src/infinite/index.ts',
'^swr/immutable$': '/src/immutable/index.ts',
'^swr/subscription$': '/src/subscription/index.ts',
'^swr/mutation$': '/src/mutation/index.ts',
'^swr/_internal$': '/src/_internal/index.ts'
},
transform: {
'^.+\\.(t|j)sx?$': '@swc/jest'
},
coveragePathIgnorePatterns: [
'/node_modules/',
'/dist/',
'/test/',
'/src/_internal/utils/env.ts'
],
coverageReporters: ['text', 'html'],
reporters: [['github-actions', { silent: false }], 'summary']
}
export default config
================================================
FILE: mutation/package.json
================================================
{
"main": "../dist/mutation/index.js",
"module": "../dist/mutation/index.mjs",
"types": "../dist/mutation/index.d.ts",
"private": true
}
================================================
FILE: package.json
================================================
{
"name": "swr",
"version": "2.4.1",
"description": "React Hooks library for remote data fetching",
"keywords": [
"swr",
"react",
"hooks",
"request",
"cache",
"fetch"
],
"packageManager": "pnpm@10.24.0",
"main": "./dist/index/index.js",
"module": "./dist/index/index.mjs",
"types": "./dist/index/index.d.ts",
"sideEffects": false,
"exports": {
"./package.json": "./package.json",
".": {
"react-server": "./dist/index/react-server.mjs",
"import": {
"types": "./dist/index/index.d.mts",
"default": "./dist/index/index.mjs"
},
"require": {
"types": "./dist/index/index.d.ts",
"default": "./dist/index/index.js"
}
},
"./infinite": {
"react-server": "./dist/infinite/react-server.mjs",
"import": {
"types": "./dist/infinite/index.d.mts",
"default": "./dist/infinite/index.mjs"
},
"require": {
"types": "./dist/infinite/index.d.ts",
"default": "./dist/infinite/index.js"
}
},
"./immutable": {
"import": {
"types": "./dist/immutable/index.d.mts",
"default": "./dist/immutable/index.mjs"
},
"require": {
"types": "./dist/immutable/index.d.ts",
"default": "./dist/immutable/index.js"
}
},
"./subscription": {
"import": {
"types": "./dist/subscription/index.d.mts",
"default": "./dist/subscription/index.mjs"
},
"require": {
"types": "./dist/subscription/index.d.ts",
"default": "./dist/subscription/index.js"
}
},
"./mutation": {
"import": {
"types": "./dist/mutation/index.d.mts",
"default": "./dist/mutation/index.mjs"
},
"require": {
"types": "./dist/mutation/index.d.ts",
"default": "./dist/mutation/index.js"
}
},
"./_internal": {
"react-server": "./dist/_internal/react-server.mjs",
"import": {
"types": "./dist/_internal/index.d.mts",
"default": "./dist/_internal/index.mjs"
},
"require": {
"types": "./dist/_internal/index.d.ts",
"default": "./dist/_internal/index.js"
}
}
},
"files": [
"dist",
"infinite",
"immutable",
"subscription",
"mutation",
"_internal"
],
"repository": "github:vercel/swr",
"homepage": "https://swr.vercel.app",
"license": "MIT",
"scripts": {
"prepare": "husky install",
"csb:install": "corepack enable && corepack pnpm i",
"csb:build": "pnpm build",
"clean": "rimraf ./dist && rimraf playwright-report test-result",
"watch": "bunchee -w",
"build": "bunchee",
"build:e2e": "pnpm next build e2e/site",
"attw": "attw --pack .",
"types:check": "tsc --noEmit",
"prepublishOnly": "pnpm clean && pnpm build",
"publish-beta": "pnpm publish --tag beta",
"format": "prettier --write ./**/*.{ts,tsx}",
"lint": "eslint . --ext .ts,.tsx --cache",
"lint:fix": "pnpm lint --fix",
"coverage": "jest --coverage",
"test-typing": "tsc -p test/tsconfig.json && tsc -p test/type/tsconfig.json && tsc -p test/type/suspense/tsconfig.json",
"test": "jest",
"test:build": "jest --config jest.config.build.js",
"test:e2e": "playwright test",
"run-all-checks": "pnpm types:check && pnpm lint && pnpm test-typing"
},
"lint-staged": {
"*.{ts,tsx}": [
"eslint --fix --cache",
"prettier --write"
]
},
"devDependencies": {
"@arethetypeswrong/cli": "^0.18.2",
"@edge-runtime/jest-environment": "^4.0.0",
"@eslint/compat": "^2.0.0",
"@eslint/eslintrc": "^3.3.1",
"@eslint/js": "^9.39.1",
"@playwright/test": "1.57.0",
"@swc/core": "^1.15.3",
"@swc/jest": "0.2.39",
"@testing-library/dom": "^10.4.1",
"@testing-library/jest-dom": "^6.9.1",
"@testing-library/react": "^16.3.0",
"@type-challenges/utils": "0.1.1",
"@types/jest": "^30.0.0",
"@types/node": "^22.19.1",
"@types/react": "^19.2.7",
"@types/use-sync-external-store": "^1.5.0",
"@typescript-eslint/eslint-plugin": "8.48.0",
"@typescript-eslint/parser": "8.48.0",
"bunchee": "^6.6.2",
"eslint": "9.39.1",
"eslint-config-prettier": "10.1.8",
"eslint-plugin-jest-dom": "5.5.0",
"eslint-plugin-react": "7.37.5",
"eslint-plugin-react-hooks": "7.0.1",
"eslint-plugin-testing-library": "7.13.5",
"globals": "^16.5.0",
"husky": "9.1.7",
"jest": "30.2.0",
"jest-environment-jsdom": "29.7.0",
"lint-staged": "16.2.7",
"next": "16.1.1",
"prettier": "2.8.8",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-error-boundary": "^5.0.0",
"rimraf": "6.1.2",
"semver": "^7.5.1",
"swr": "workspace:*",
"typescript": "5.9.3",
"typescript-eslint": "^8.48.0"
},
"peerDependencies": {
"react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
},
"prettier": {
"tabWidth": 2,
"semi": false,
"useTabs": false,
"singleQuote": true,
"arrowParens": "avoid",
"trailingComma": "none"
},
"dependencies": {
"dequal": "^2.0.3",
"use-sync-external-store": "^1.6.0"
}
}
================================================
FILE: playwright.config.js
================================================
import { defineConfig, devices } from '@playwright/test'
export default defineConfig({
webServer: {
command: 'pnpm next start e2e/site --port 4000',
reuseExistingServer: !process.env.CI,
port: 4000
},
testDir: './e2e',
/* The base directory, relative to the config file, for snapshot files created with toMatchSnapshot and toHaveScreenshot. */
snapshotDir: './e2e/__snapshots__',
/* Maximum time one test can run for. */
timeout: 10 * 1000,
/* Run tests in files in parallel */
fullyParallel: true,
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
/* Retry on CI only */
retries: process.env.CI ? 2 : 0,
/* Opt out of parallel tests on CI. */
workers: process.env.CI ? 1 : undefined,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: process.env.CI
? [['github'], ['html', { open: 'on-failure' }]]
: [['html', { open: 'on-failure' }]],
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
baseURL: 'http://localhost:4000',
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: process.env.CI ? 'on-first-retry' : 'on',
...devices['Desktop Chrome']
}
})
================================================
FILE: pnpm-workspace.yaml
================================================
packages:
- '_internal'
- 'core'
- 'immutable'
- 'infinite'
- 'mutation'
================================================
FILE: scripts/bump-next-version.js
================================================
const fs = require('fs')
const path = require('path')
const { execSync } = require('child_process')
const semver = require('semver')
const packageJsonPath = path.join(__dirname, '../package.json')
const packageJsonData = fs.readFileSync(packageJsonPath, 'utf8')
const packageJson = JSON.parse(packageJsonData)
let version = packageJson.version
const releaseType = process.env.RELEASE_TYPE || 'beta'
const semverType = process.env.SEMVER_TYPE
function bumpVersion(version) {
if (process.env.DRY_RUN) {
console.log(`npm version ${version}`)
} else {
try {
execSync(`npm version ${version}`, { stdio: 'inherit' })
} catch (error) {
console.error('Failed to execute npm version:', error)
process.exit(1)
}
}
}
if (releaseType === 'beta') {
if (semver.prerelease(version)) {
version = semver.inc(version, 'prerelease')
} else {
version = semver.inc(version, 'pre' + semverType, 'beta')
}
} else if (releaseType === 'stable') {
if (!semverType) {
console.error('Missing semver type. Expected "patch", "minor" or "major".')
process.exit(1)
}
version = semver.inc(version, semverType)
} else {
console.error('Invalid release type. Expected "beta" or "stable".')
process.exit(1)
}
bumpVersion(version)
================================================
FILE: src/_internal/constants.ts
================================================
export const INFINITE_PREFIX = '$inf$'
================================================
FILE: src/_internal/events.ts
================================================
export const FOCUS_EVENT = 0
export const RECONNECT_EVENT = 1
export const MUTATE_EVENT = 2
export const ERROR_REVALIDATE_EVENT = 3
================================================
FILE: src/_internal/index.react-server.ts
================================================
export { serialize } from './utils/serialize'
export { SWRConfig } from './index'
export { INFINITE_PREFIX } from './constants'
================================================
FILE: src/_internal/index.ts
================================================
import SWRConfig from './utils/config-context'
import * as revalidateEvents from './events'
import { INFINITE_PREFIX } from './constants'
export { SWRConfig, revalidateEvents, INFINITE_PREFIX }
export { initCache } from './utils/cache'
export { defaultConfig, cache, mutate, compare } from './utils/config'
import { setupDevTools } from './utils/devtools'
export * from './utils/env'
export { SWRGlobalState } from './utils/global-state'
export { stableHash } from './utils/hash'
export * from './utils/helper'
export * from './utils/shared'
export { mergeConfigs } from './utils/merge-config'
export { internalMutate } from './utils/mutate'
export { normalize } from './utils/normalize-args'
export { withArgs } from './utils/resolve-args'
export { serialize } from './utils/serialize'
export { subscribeCallback } from './utils/subscribe-key'
export { getTimestamp } from './utils/timestamp'
export { useSWRConfig } from './utils/use-swr-config'
export { preset, defaultConfigOptions } from './utils/web-preset'
export { withMiddleware } from './utils/with-middleware'
export { preload } from './utils/preload'
export * from './types'
setupDevTools()
================================================
FILE: src/_internal/types.ts
================================================
import type { SWRGlobalConfig } from '../index'
import type * as revalidateEvents from './events'
/**
* Global state tuple containing SWR's internal state management structures.
*
* This is the core state structure that manages all SWR operations internally.
* Each element serves a specific purpose in the SWR ecosystem.
*
* @internal
*/
export type GlobalState = [
/** Event revalidators: Maps cache keys to arrays of revalidation callbacks */
Record,
/** Mutation timestamps: Maps cache keys to [start_timestamp, end_timestamp] tuples */
Record,
/** Fetch cache: Maps cache keys to [data, timestamp] tuples */
Record,
/** Preload cache: Maps cache keys to fetcher responses */
Record>,
/** Scoped mutator function for cache updates */
ScopedMutator,
/** Cache setter function with prev/current value comparison */
(key: string, value: any, prev: any) => void,
/** Cache subscriber function that returns an unsubscribe function */
(key: string, callback: (current: any, prev: any) => void) => () => void
]
/**
* Response type that can be returned by fetcher functions.
*
* @template Data - The type of data returned by the fetcher
* @public
*/
export type FetcherResponse = Data | Promise
/**
* Basic fetcher function that accepts any arguments and returns data or a promise.
*
* This is the most permissive fetcher type, allowing any number of arguments
* of any type. Used when type safety is not required or when dealing with
* dynamic fetcher signatures.
*
* @template Data - The type of data returned by the fetcher
* @param args - Variable arguments passed to the fetcher
* @returns Data or a Promise that resolves to data
* @public
*/
export type BareFetcher = (
...args: any[]
) => FetcherResponse
/**
* Typed fetcher function that is constrained by the SWR key type.
*
* Provides type safety by ensuring the fetcher argument matches the key type.
* The conditional type logic ensures that:
* - If the key is a function returning a value, the fetcher receives that value
* - If the key is falsy (null, undefined, false), the fetcher is never called
* - Otherwise, the fetcher receives the key directly as its argument
*
* @template Data - The type of data returned by the fetcher
* @template SWRKey - The type of the SWR key, used to infer fetcher arguments
* @public
*/
export type Fetcher<
Data = unknown,
SWRKey extends Key = Key
> = SWRKey extends () => infer Arg | null | undefined | false
? (arg: Arg) => FetcherResponse
: SWRKey extends null | undefined | false
? never
: SWRKey extends infer Arg
? (arg: Arg) => FetcherResponse
: never
/**
* Determines if data should block rendering based on suspense configuration.
*
* This conditional type is used internally to determine the return type of `data`
* in SWRResponse. When suspense is enabled or fallbackData is provided, data
* will never be undefined, allowing for non-nullable return types.
*
* The type resolution follows this logic:
* 1. If global suspense is enabled → `true` (data never undefined)
* 2. If no options provided → `false` (data can be undefined)
* 3. If suspense is enabled in options → `true` (data never undefined)
* 4. If fallbackData is provided → `true` (data never undefined)
* 5. Otherwise → `false` (data can be undefined)
*
* @template Data - The data type
* @template Options - The SWR configuration options
* @returns `true` if data is guaranteed to be defined, `false` if it can be undefined
* @internal
*/
export type BlockingData<
Data = any,
Options = SWRDefaultOptions
> = SWRGlobalConfig extends { suspense: true }
? true
: Options extends undefined
? false
: Options extends { suspense: true }
? true
: Options extends { fallbackData: Data | Promise }
? true
: false
/**
* Configuration types that are only used internally, not exposed to the user.
*
* These options are managed internally by SWR and passed between internal
* functions. They are not part of the public API and should not be used
* directly by consumers.
*
* @internal
*/
export interface InternalConfiguration {
/** The cache instance used to store SWR data and state */
cache: Cache
/** Scoped mutator function for updating cache entries */
mutate: ScopedMutator
}
/**
* Public configuration options for SWR.
*
* This interface defines all the configuration options that users can pass
* to customize SWR's behavior. These options can be provided globally via
* SWRConfig or per-hook via the config parameter.
*
* @template Data - The type of data returned by the fetcher
* @template Error - The type of error that can be thrown
* @template Fn - The fetcher function type
*
* @public
* @see {@link https://swr.vercel.app/docs/options | SWR Options Documentation}
*/
export interface PublicConfiguration<
Data = any,
Error = any,
Fn extends Fetcher = BareFetcher
> {
/**
* error retry interval in milliseconds
* @defaultValue 5000
*/
errorRetryInterval: number
/** max error retry count */
errorRetryCount?: number
/**
* timeout to trigger the onLoadingSlow event in milliseconds
* @defaultValue 3000
*/
loadingTimeout: number
/**
* only revalidate once during a time span in milliseconds
* @defaultValue 5000
*/
focusThrottleInterval: number
/**
* dedupe requests with the same key in this time span in milliseconds
* @defaultValue 2000
*/
dedupingInterval: number
/**
* * Disabled by default: `refreshInterval = 0`
* * If set to a number, polling interval in milliseconds
* * If set to a function, the function will receive the latest data and should return the interval in milliseconds
* @see {@link https://swr.vercel.app/docs/revalidation}
*/
refreshInterval?: number | ((latestData: Data | undefined) => number)
/**
* polling when the window is invisible (if `refreshInterval` is enabled)
* @defaultValue false
*/
refreshWhenHidden?: boolean
/**
* polling when the browser is offline (determined by `navigator.onLine`)
*
* When enabled, SWR will continue polling even when the browser is offline.
* This can be useful for applications that need to check for connectivity
* or cache updates while offline.
*
* @defaultValue false
*/
refreshWhenOffline?: boolean
/**
* automatically revalidate when window gets focused
*
* When enabled, SWR will automatically revalidate data when the user
* returns focus to the window/tab. This ensures data freshness when
* users switch between applications.
*
* @defaultValue true
* @see {@link https://swr.vercel.app/docs/revalidation | Revalidation Documentation}
*/
revalidateOnFocus: boolean
/**
* automatically revalidate when the browser regains a network connection (via `navigator.onLine`)
*
* When enabled, SWR will automatically revalidate data when the browser
* goes from offline to online state, ensuring data is up-to-date when
* connectivity is restored.
*
* @defaultValue true
* @see {@link https://swr.vercel.app/docs/revalidation | Revalidation Documentation}
*/
revalidateOnReconnect: boolean
/**
* enable or disable automatic revalidation when component is mounted
*
* Controls whether SWR should automatically fetch data when the component
* mounts. When `undefined`, the behavior depends on `revalidateIfStale`.
*
* @defaultValue undefined (inherits from revalidateIfStale)
*/
revalidateOnMount?: boolean
/**
* automatically revalidate even if there is stale data
* @defaultValue true
* @see {@link https://swr.vercel.app/docs/revalidation#disable-automatic-revalidations}
*/
revalidateIfStale: boolean
/**
* retry when fetcher has an error
* @defaultValue true
*/
shouldRetryOnError: boolean | ((err: Error) => boolean)
/**
* keep the previous result when key is changed but data is not ready
* @defaultValue false
*/
keepPreviousData?: boolean
/**
* @experimental enable React Suspense mode
* @defaultValue false
* @see {@link https://swr.vercel.app/docs/suspense}
*/
suspense?: boolean
/**
* initial data to be returned (note: ***This is per-hook***)
* @see {@link https://swr.vercel.app/docs/with-nextjs}
*/
fallbackData?: Data | Promise
/**
* warns when preload data is missing for a given key, this includes fallback
* data, preload calls, or initial data from the cache provider
* @defaultValue false
*/
strictServerPrefetchWarning?: boolean
/**
* the fetcher function
*/
fetcher?: Fn
/**
* array of middleware functions
* @see {@link https://swr.vercel.app/docs/middleware}
*/
use?: Middleware[]
/**
* a key-value object of multiple fallback data
* @see {@link https://swr.vercel.app/docs/with-nextjs#pre-rendering-with-default-data}
*/
fallback: { [key: string]: any }
/**
* Function to detect whether pause revalidations, will ignore fetched data and errors when it returns true. Returns false by default.
*/
isPaused: () => boolean
/**
* callback function when a request takes too long to load (see `loadingTimeout`)
*/
onLoadingSlow: (
key: string,
config: Readonly>
) => void
/**
* callback function when a request finishes successfully
*/
onSuccess: (
data: Data,
key: string,
config: Readonly>
) => void
/**
* callback function when a request returns an error
*/
onError: (
err: Error,
key: string,
config: Readonly>
) => void
/**
* handler for error retry
*/
onErrorRetry: (
err: Error,
key: string,
config: Readonly>,
revalidate: Revalidator,
revalidateOpts: Required
) => void
/**
* callback function when a request is ignored due to race conditions
*/
onDiscarded: (key: string) => void
/**
* Comparison function used to detect when returned data has changed, to avoid spurious rerenders. By default, [dequal](https://github.com/lukeed/dequal) is used.
*/
compare: (a: Data | undefined, b: Data | undefined) => boolean
/**
* 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.
* @see {@link https://swr.vercel.app/docs/advanced/react-native#customize-focus-and-reconnect-events}
*/
isOnline: () => boolean
/**
* 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.
* @see {@link https://swr.vercel.app/docs/advanced/react-native#customize-focus-and-reconnect-events}
*/
isVisible: () => boolean
}
export type FullConfiguration<
Data = any,
Error = any,
Fn extends Fetcher = BareFetcher
> = InternalConfiguration & PublicConfiguration
/**
* Provider configuration for custom focus and reconnect event handling.
*
* This configuration allows custom implementations for detecting window focus
* and network reconnection events. Useful for React Native or other environments
* where the default browser APIs are not available.
*
* @public
* @see {@link https://swr.vercel.app/docs/advanced/react-native | React Native Documentation}
*/
export type ProviderConfiguration = {
/**
* Initialize focus event listener.
*
* @param callback - Function to call when window gains focus
* @returns Optional cleanup function to remove the listener
*/
initFocus: (callback: () => void) => (() => void) | void
/**
* Initialize reconnect event listener.
*
* @param callback - Function to call when network reconnects
* @returns Optional cleanup function to remove the listener
*/
initReconnect: (callback: () => void) => (() => void) | void
}
/**
* The main useSWR hook interface with multiple overloads for different usage patterns.
*
* This interface provides type-safe overloads for various ways to call useSWR,
* from simple key-only calls to complex configurations with custom fetchers.
* The overloads ensure proper type inference for data, error, and configuration.
*
* @public
*
* @example Basic usage
* ```ts
* const { data, error } = useSWR('/api/data', fetcher)
* ```
*
* @example With configuration
* ```ts
* const { data, error } = useSWR('/api/data', fetcher, {
* refreshInterval: 1000,
* revalidateOnFocus: false
* })
* ```
*
* @example Conditional fetching
* ```ts
* const { data, error } = useSWR(
* user.id ? `/api/user/${user.id}` : null,
* fetcher
* )
* ```
*
* @example Dynamic key with function
* ```ts
* const { data, error } = useSWR(
* () => user.id ? [`/api/user/${user.id}`, user.token] : null,
* ([url, token]) => fetcher(url, { headers: { Authorization: token } })
* )
* ```
*/
export interface SWRHook {
/**
* Basic usage with just a key. Requires a global fetcher to be configured,
* or can be used for client-side state management without fetching.
*
* @example
* ```ts
* // With global fetcher
* const { data } = useSWR('/api/user')
*
* // Client state management
* const { data, mutate } = useSWR('user-settings')
* mutate({ theme: 'dark' })
* ```
*/
(
key: SWRKey
): SWRResponse
/**
* Most common usage pattern with key and explicit fetcher function.
* The fetcher receives the key as its argument and returns the data.
*
* @example
* ```ts
* const { data, error } = useSWR('/api/user/123',
* (url) => fetch(url).then(res => res.json())
* )
* ```
*/
(
key: SWRKey,
fetcher: Fetcher | null
): SWRResponse
/**
* Key with fetcher using relaxed key constraints for dynamic or complex keys.
* Allows more flexible key types including functions and objects.
*
* @example
* ```ts
* const { data } = useSWR(
* () => user ? ['/api/posts', user.id] : null,
* ([url, userId]) => fetchUserPosts(url, userId)
* )
* ```
*/
(
key: SWRKey,
fetcher: Fetcher | null
): SWRResponse
/**
* Key-only with advanced configuration options and strict typing.
* Useful when you need specific SWR options but rely on a global fetcher.
*
* @example
* ```ts
* const { data } = useSWR('/api/user', {
* refreshInterval: 5000,
* revalidateOnFocus: false
* })
* ```
*/
<
Data = any,
Error = any,
SWRKey extends Key = StrictKey,
SWROptions extends
| SWRConfiguration>
| undefined =
| SWRConfiguration>
| undefined
>(
key: SWRKey
): SWRResponse
/**
* Key with fetcher and advanced configuration options with strict typing.
* Provides full control over fetching behavior and SWR options.
*
* @example
* ```ts
* const { data } = useSWR('/api/data', fetcher, {
* suspense: true,
* fallbackData: initialData
* })
* ```
*/
<
Data = any,
Error = any,
SWRKey extends Key = StrictKey,
SWROptions extends
| SWRConfiguration>
| undefined =
| SWRConfiguration>
| undefined
>(
key: SWRKey,
fetcher: Fetcher | null
): SWRResponse
/**
* Key with configuration object but no explicit fetcher. Uses global fetcher
* or can be used for pure client state management with configuration.
*
* @example
* ```ts
* // With global fetcher and config
* const { data } = useSWR('/api/user', {
* refreshInterval: 1000
* })
*
* // Client state with config
* const { data } = useSWR('local-state', {
* fallbackData: defaultValue
* })
* ```
*/
<
Data = any,
Error = any,
SWRKey extends Key = StrictKey,
SWROptions extends
| SWRConfiguration>
| undefined =
| SWRConfiguration>
| undefined
>(
key: SWRKey,
config: SWRConfigurationWithOptionalFallback
): SWRResponse
/**
* Complete signature with key, fetcher, and configuration options.
* Provides maximum flexibility and control over all SWR behavior.
*
* @example
* ```ts
* const { data, error, isLoading } = useSWR(
* '/api/user',
* async (url) => {
* const res = await fetch(url)
* if (!res.ok) throw new Error('Failed to fetch')
* return res.json()
* },
* {
* refreshInterval: 5000,
* onError: (error) => console.error('SWR Error:', error),
* fallbackData: { name: 'Loading...' }
* }
* )
* ```
*/
<
Data = any,
Error = any,
SWRKey extends Key = StrictKey,
SWROptions extends
| SWRConfiguration>
| undefined =
| SWRConfiguration>
| undefined
>(
key: SWRKey,
fetcher: Fetcher | null,
config: SWRConfigurationWithOptionalFallback
): SWRResponse
/**
* Simple key-only usage with flexible key types. Most permissive overload
* that accepts any valid key format.
*
* @example
* ```ts
* const { data } = useSWR('/api/data')
* const { data: userData } = useSWR(['user', userId])
* const { data: settings } = useSWR({ endpoint: '/settings', version: 'v1' })
* ```
*/
(key: Key): SWRResponse
/**
* Key-only with configuration options using bare fetcher constraints.
* Suitable for cases where fetcher type safety is less important.
*
* @example
* ```ts
* const { data } = useSWR('/api/data', {
* dedupingInterval: 5000
* })
* ```
*/
<
Data = any,
Error = any,
SWROptions extends
| SWRConfiguration>
| undefined = SWRConfiguration> | undefined
>(
key: Key
): SWRResponse
/**
* Key with bare fetcher function that accepts any arguments.
* Provides less type safety but maximum flexibility for fetcher signatures.
*
* @example
* ```ts
* const { data } = useSWR('/api/user',
* (...args) => customFetcher(...args)
* )
* ```
*/
<
Data = any,
Error = any,
SWROptions extends
| SWRConfiguration>
| undefined = SWRConfiguration> | undefined
>(
key: Key,
fetcher: BareFetcher | null
): SWRResponse
/**
* Key with configuration using relaxed fetcher typing constraints.
* Useful when working with dynamic or loosely-typed fetcher functions.
*
* @example
* ```ts
* const { data } = useSWR(dynamicKey, {
* fetcher: customFetcher,
* refreshInterval: 2000
* })
* ```
*/
<
Data = any,
Error = any,
SWROptions extends
| SWRConfiguration>
| undefined = SWRConfiguration> | undefined
>(
key: Key,
config: SWRConfigurationWithOptionalFallback
): SWRResponse
/**
* Complete signature with key, bare fetcher, and configuration.
* Most flexible overload with minimal type constraints, suitable for
* complex scenarios where strict typing isn't feasible.
*
* @example
* ```ts
* const { data } = useSWR(
* complexKey,
* (...args) => legacyFetcher(...args),
* {
* refreshInterval: 10000,
* errorRetryCount: 3
* }
* )
* ```
*/
<
Data = any,
Error = any,
SWROptions extends
| SWRConfiguration>
| undefined = SWRConfiguration> | undefined
>(
key: Key,
fetcher: BareFetcher | null,
config: SWRConfigurationWithOptionalFallback
): SWRResponse
}
/**
* Middleware function type for extending SWR functionality.
*
* Middleware functions receive the next SWR hook in the chain and return
* a modified hook function. This allows for composition of multiple
* middleware functions to add features like logging, caching, or
* request/response transformation.
*
* The middleware guarantees that a SWRHook receives a key, fetcher,
* and config as arguments, providing a consistent interface for
* middleware authors.
*
* @param useSWRNext - The next SWR hook function in the middleware chain
* @returns A new SWR hook function with middleware functionality applied
*
* @template Data - The type of data returned by the fetcher
* @template Error - The type of error that can be thrown
*
* @public
* @see {@link https://swr.vercel.app/docs/middleware | Middleware Documentation}
*
* @example
* ```ts
* const logger: Middleware = (useSWRNext) => (key, fetcher, config) => {
* console.log('SWR Request:', key)
* return useSWRNext(key, fetcher, config)
* }
* ```
*/
export type Middleware = (
useSWRNext: SWRHook
) => (
key: Key,
fetcher: BareFetcher | null,
config: SWRConfiguration>
) => SWRResponse
/**
* Represents a tuple of arguments that can be passed to a fetcher.
*
* The first element is typically the primary key (like a URL), followed
* by additional parameters that affect the request (like query parameters,
* headers, or request options).
*
*/
type ArgumentsTuple = readonly [any, ...unknown[]]
/**
* Valid types for SWR keys.
*
* SWR keys identify unique requests and can be:
* - `string`: Simple URL or identifier
* - `ArgumentsTuple`: Array with URL and additional parameters
* - `Record`: Object that will be serialized
* - `null | undefined | false`: Falsy values disable the request
*
* When a key is falsy, SWR will not make the request, allowing for
* conditional fetching based on application state.
*
* @public
*
* @example
* ```ts
* // String key
* useSWR('/api/users', fetcher)
*
* // Array key with parameters
* useSWR(['/api/user', userId], ([url, id]) => fetcher(`${url}/${id}`))
*
* // Object key
* useSWR({ url: '/api/data', params: { page: 1 } }, fetcher)
*
* // Conditional key
* useSWR(userId ? `/api/user/${userId}` : null, fetcher)
* ```
*/
export type Arguments =
| string
| ArgumentsTuple
| Record
| null
| undefined
| false
/**
* SWR key that can be static or a function that returns arguments.
*
* When a function is provided, it's called on each render to determine
* the current key. This allows for dynamic keys based on component state
* or props.
*
* @public
*
* @example
* ```ts
* // Static key
* useSWR('/api/data', fetcher)
*
* // Dynamic key function
* useSWR(() => user ? `/api/user/${user.id}` : null, fetcher)
* ```
*/
export type Key = Arguments | (() => Arguments)
/**
* Strict tuple key type that only allows tuples or falsy values.
*
* @internal
*/
export type StrictTupleKey = ArgumentsTuple | null | undefined | false
/**
* Strict key type for internal use.
*
* @internal
*/
type StrictKey = StrictTupleKey | (() => StrictTupleKey)
/**
* Callback function type for mutator operations.
*
* This function receives the current cached data and can return new data
* to update the cache. It can be synchronous or asynchronous, and can
* return undefined to indicate no change should be made.
*
* @template Data - The type of the cached data
* @param currentData - The current data in the cache (may be undefined)
* @returns New data to set, undefined for no change, or a Promise resolving to either
*
* @public
*
* @example
* ```ts
* // Increment a counter
* mutate(key, (current: number = 0) => current + 1)
*
* // Async update
* mutate(key, async (current) => {
* const updated = await updateData(current)
* return updated
* })
* ```
*/
export type MutatorCallback = (
currentData?: Data
) => Promise | undefined | Data
/**
* Options for configuring mutator behavior.
*
* These options control how the mutation affects the cache, revalidation,
* and error handling behavior.
*
* @template Data - The type of the data related to the key
* @template MutationData - The type of the data returned by the mutator
*
* @public
*/
export type MutatorOptions = {
/**
* Whether to revalidate the cache after mutation.
*
* Can be a boolean or a function that receives the new data and key
* to determine whether revalidation should occur.
*
* @defaultValue true
*/
revalidate?: boolean | ((data: Data, key: Arguments) => boolean)
/**
* Whether and how to populate the cache with mutation results.
*
* - `false`: Don't update the cache
* - `true`: Update cache with mutation result directly
* - Function: Transform mutation result before caching
*
* @defaultValue true
*/
populateCache?:
| boolean
| ((result: MutationData, currentData: Data | undefined) => Data)
/**
* Optimistic data to show immediately while mutation is pending.
*
* Can be the data directly or a function that computes it based on
* current and displayed data. Useful for immediate UI feedback.
*
* @defaultValue undefined
*/
optimisticData?:
| Data
| ((currentData: Data | undefined, displayedData: Data | undefined) => Data)
/**
* Whether to rollback optimistic updates on error.
*
* Can be a boolean or a function that receives the error to determine
* whether rollback should occur.
*
* @defaultValue true
*/
rollbackOnError?: boolean | ((error: unknown) => boolean)
/**
* Whether to throw errors instead of returning them in the error field.
*
* When true, errors will be thrown and can be caught with try/catch.
* When false, errors are returned in the response object.
*
* @defaultValue false
*/
throwOnError?: boolean
}
export type MutatorConfig = {
revalidate?: boolean
populateCache?: boolean
}
export type Broadcaster = (
cache: Cache,
key: string,
data: Data,
error?: Error,
isValidating?: boolean,
revalidate?: boolean,
populateCache?: boolean
) => Promise
/**
* Internal state structure stored in the cache.
*
* This represents the complete state for a cache entry, including
* data, error, and loading states. All fields are optional as they
* may not be present depending on the request lifecycle.
*
* @template Data - The type of data stored
* @template Error - The type of error that can occur
*
* @internal
*/
export type State = {
/** The cached data, if available */
data?: Data
/** The error object, if an error occurred */
error?: Error
/** Whether a revalidation is currently in progress */
isValidating?: boolean
/** Whether this is the initial load with no cached data */
isLoading?: boolean
}
export type MutatorFn = (
cache: Cache,
key: Key,
data?: Data | Promise | MutatorCallback,
opts?: boolean | MutatorOptions
) => Promise
export type MutatorWrapper = Fn extends (
...args: [...infer Parameters]
) => infer Result
? Parameters[3] extends boolean
? Result
: Parameters[3] extends Required>
? Parameters[3]['populateCache'] extends false
? never
: Result
: Result
: never
export type Mutator = MutatorWrapper>
export interface ScopedMutator {
/**
* @typeParam Data - The type of the data related to the key
* @typeParam MutationData - The type of the data returned by the mutator
*/
(
matcher: (key?: Arguments) => boolean,
data?: MutationData | Promise | MutatorCallback,
opts?: boolean | MutatorOptions
): Promise>
/**
* @typeParam Data - The type of the data related to the key
* @typeParam MutationData - The type of the data returned by the mutator
*/
(
key: Arguments,
data?: T | Promise | MutatorCallback,
opts?: boolean | MutatorOptions
): Promise
}
/**
* @typeParam Data - The type of the data related to the key
* @typeParam MutationData - The type of the data returned by the mutator
*/
export type KeyedMutator = (
data?: Data | Promise | MutatorCallback,
opts?: boolean | MutatorOptions
) => Promise
export type SWRConfiguration<
Data = any,
Error = any,
Fn extends BareFetcher = BareFetcher
> = Partial> &
Partial & {
provider?: (cache: Readonly) => Cache
}
export type IsLoadingResponse<
Data = any,
Options = SWRDefaultOptions
> = SWRGlobalConfig extends { suspense: true }
? Options extends { suspense: true }
? false
: false
: boolean
type SWRDefaultOptions = SWRConfiguration>
type SWRConfigurationWithOptionalFallback =
// If `Options` has `fallbackData`, this turns it to optional instead.
Options extends SWRConfiguration &
Required>
? Omit & Pick, 'fallbackData'>
: Options
/**
* The response object returned by SWR hooks.
*
* This interface represents the return value of useSWR and related hooks,
* providing access to data, error state, and control functions.
*
* @template Data - The type of data returned by the fetcher
* @template Error - The type of error that can be thrown
* @template Config - The configuration type used to determine blocking behavior
*
* @public
*/
export interface SWRResponse {
/**
* The data returned by the fetcher function.
*
* - When suspense is enabled or fallbackData is provided: always defined
* - Otherwise: `undefined` during initial load or when key is falsy
*/
data: BlockingData extends true ? Data : Data | undefined
/**
* The error object thrown by the fetcher function.
*
* `undefined` when there's no error or when a request is in progress.
*/
error: Error | undefined
/**
* Function to mutate the cached data for this specific key.
*
* This is a bound version of the global mutate function that automatically
* uses the current key, providing type safety and convenience.
*/
mutate: KeyedMutator
/**
* Whether the request is currently being validated (loading fresh data).
*
* `true` during initial load, revalidation, or when mutate is called
* with a promise or async function.
*/
isValidating: boolean
/**
* Whether the request is in initial loading state.
*
* `true` only during the initial load when there's no cached data.
* Unlike `isValidating`, this becomes `false` once data is available.
*/
isLoading: IsLoadingResponse
}
export type KeyLoader =
| ((index: number, previousPageData: any | null) => Args)
| null
export interface RevalidatorOptions {
retryCount?: number
dedupe?: boolean
}
export type Revalidator = (
revalidateOpts?: RevalidatorOptions
) => Promise | void
export type RevalidateEvent =
| typeof revalidateEvents.FOCUS_EVENT
| typeof revalidateEvents.RECONNECT_EVENT
| typeof revalidateEvents.MUTATE_EVENT
| typeof revalidateEvents.ERROR_REVALIDATE_EVENT
type RevalidateCallbackReturnType = {
[revalidateEvents.FOCUS_EVENT]: void
[revalidateEvents.RECONNECT_EVENT]: void
[revalidateEvents.MUTATE_EVENT]: Promise
[revalidateEvents.ERROR_REVALIDATE_EVENT]: void
}
export type RevalidateCallback = (
type: K,
opts?: any
) => RevalidateCallbackReturnType[K]
/**
* Cache interface for storing SWR state.
*
* This interface defines the contract for cache providers used by SWR.
* The default implementation uses a Map, but custom cache providers
* can implement this interface to provide different storage mechanisms.
*
* @template Data - The type of data stored in the cache
*
* @public
* @see {@link https://swr.vercel.app/docs/advanced/cache | Cache Documentation}
*/
export interface Cache {
/**
* Get an iterator of all cache keys.
*
* @returns Iterator that yields all cache keys as strings
*/
keys(): IterableIterator
/**
* Get the cached state for a key.
*
* @param key - The cache key to look up
* @returns The cached state or undefined if not found
*/
get(key: string): State | undefined
/**
* Set the cached state for a key.
*
* @param key - The cache key to set
* @param value - The state to cache
*/
set(key: string, value: State): void
/**
* Delete a cached entry.
*
* @param key - The cache key to delete
*/
delete(key: string): void
}
export interface StateDependencies {
data?: boolean
error?: boolean
isValidating?: boolean
isLoading?: boolean
}
================================================
FILE: src/_internal/utils/cache.ts
================================================
import { defaultConfigOptions } from './web-preset'
import { IS_SERVER } from './env'
import { UNDEFINED, mergeObjects, noop } from './shared'
import { internalMutate } from './mutate'
import { SWRGlobalState } from './global-state'
import * as revalidateEvents from '../events'
import type {
Cache,
ScopedMutator,
RevalidateEvent,
RevalidateCallback,
ProviderConfiguration,
GlobalState
} from '../types'
const revalidateAllKeys = (
revalidators: Record,
type: RevalidateEvent
) => {
for (const key in revalidators) {
if (revalidators[key][0]) revalidators[key][0](type)
}
}
export const initCache = (
provider: Cache,
options?: Partial
):
| [Cache, ScopedMutator, () => void, () => void]
| [Cache, ScopedMutator]
| undefined => {
// The global state for a specific provider will be used to deduplicate
// requests and store listeners. As well as a mutate function that is bound to
// the cache.
// The provider's global state might be already initialized. Let's try to get the
// global state associated with the provider first.
if (!SWRGlobalState.has(provider)) {
const opts = mergeObjects(defaultConfigOptions, options)
// If there's no global state bound to the provider, create a new one with the
// new mutate function.
const EVENT_REVALIDATORS = Object.create(null)
const mutate = internalMutate.bind(UNDEFINED, provider) as ScopedMutator
let unmount = noop
const subscriptions: Record void)[]> =
Object.create(null)
const subscribe = (
key: string,
callback: (current: any, prev: any) => void
) => {
const subs = subscriptions[key] || []
subscriptions[key] = subs
subs.push(callback)
return () => subs.splice(subs.indexOf(callback), 1)
}
const setter = (key: string, value: any, prev: any) => {
provider.set(key, value)
const subs = subscriptions[key]
if (subs) {
for (const fn of subs) {
fn(value, prev)
}
}
}
const initProvider = () => {
if (!SWRGlobalState.has(provider)) {
// Update the state if it's new, or if the provider has been extended.
SWRGlobalState.set(provider, [
EVENT_REVALIDATORS,
Object.create(null),
Object.create(null),
Object.create(null),
mutate,
setter,
subscribe
])
if (!IS_SERVER) {
// When listening to the native events for auto revalidations,
// we intentionally put a delay (setTimeout) here to make sure they are
// fired after immediate JavaScript executions, which can be
// React's state updates.
// This avoids some unnecessary revalidations such as
// https://github.com/vercel/swr/issues/1680.
const releaseFocus = opts.initFocus(
setTimeout.bind(
UNDEFINED,
revalidateAllKeys.bind(
UNDEFINED,
EVENT_REVALIDATORS,
revalidateEvents.FOCUS_EVENT
)
)
)
const releaseReconnect = opts.initReconnect(
setTimeout.bind(
UNDEFINED,
revalidateAllKeys.bind(
UNDEFINED,
EVENT_REVALIDATORS,
revalidateEvents.RECONNECT_EVENT
)
)
)
unmount = () => {
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
releaseFocus && releaseFocus()
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
releaseReconnect && releaseReconnect()
// When un-mounting, we need to remove the cache provider from the state
// storage too because it's a side-effect. Otherwise, when re-mounting we
// will not re-register those event listeners.
SWRGlobalState.delete(provider)
}
}
}
}
initProvider()
// This is a new provider, we need to initialize it and setup DOM events
// listeners for `focus` and `reconnect` actions.
// We might want to inject an extra layer on top of `provider` in the future,
// such as key serialization, auto GC, etc.
// For now, it's just a `Map` interface without any modifications.
return [provider, mutate, initProvider, unmount]
}
return [provider, (SWRGlobalState.get(provider) as GlobalState)[4]]
}
================================================
FILE: src/_internal/utils/config-context.ts
================================================
'use client'
import type { FC, PropsWithChildren } from 'react'
import {
createContext,
createElement,
useContext,
useMemo,
useRef
} from 'react'
import { cache as defaultCache } from './config'
import { initCache } from './cache'
import { mergeConfigs } from './merge-config'
import { UNDEFINED, mergeObjects, isFunction } from './shared'
import { useIsomorphicLayoutEffect } from './env'
import type { SWRConfiguration, FullConfiguration } from '../types'
export const SWRConfigContext = createContext>({})
const SWRConfig: FC<
PropsWithChildren<{
value?:
| SWRConfiguration
| ((parentConfig?: SWRConfiguration) => SWRConfiguration)
}>
> = props => {
const { value } = props
const parentConfig = useContext(SWRConfigContext)
const isFunctionalConfig = isFunction(value)
const config = useMemo(
() => (isFunctionalConfig ? value(parentConfig) : value),
[isFunctionalConfig, parentConfig, value]
)
// Extend parent context values and middleware.
const extendedConfig = useMemo(
() => (isFunctionalConfig ? config : mergeConfigs(parentConfig, config)),
[isFunctionalConfig, parentConfig, config]
)
// Should not use the inherited provider.
const provider = config && config.provider
// initialize the cache only on first access.
const cacheContextRef = useRef>(UNDEFINED)
if (provider && !cacheContextRef.current) {
cacheContextRef.current = initCache(
provider((extendedConfig as any).cache || defaultCache),
config
)
}
const cacheContext = cacheContextRef.current
// Override the cache if a new provider is given.
if (cacheContext) {
;(extendedConfig as any).cache = cacheContext[0]
;(extendedConfig as any).mutate = cacheContext[1]
}
// Unsubscribe events.
useIsomorphicLayoutEffect(() => {
if (cacheContext) {
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
cacheContext[2] && cacheContext[2]()
return cacheContext[3]
}
}, [])
return createElement(
SWRConfigContext.Provider,
mergeObjects(props, {
value: extendedConfig
})
)
}
export default SWRConfig
================================================
FILE: src/_internal/utils/config.ts
================================================
import type {
PublicConfiguration,
FullConfiguration,
RevalidatorOptions,
Revalidator,
ScopedMutator,
Cache
} from '../types'
import { initCache } from './cache'
import { preset } from './web-preset'
import { slowConnection } from './env'
import { isUndefined, noop, mergeObjects } from './shared'
import { dequal } from 'dequal/lite'
// error retry
const onErrorRetry = (
_: unknown,
__: string,
config: Readonly,
revalidate: Revalidator,
opts: Required
): void => {
const maxRetryCount = config.errorRetryCount
const currentRetryCount = opts.retryCount
// Exponential backoff
const timeout =
~~(
(Math.random() + 0.5) *
(1 << (currentRetryCount < 8 ? currentRetryCount : 8))
) * config.errorRetryInterval
if (!isUndefined(maxRetryCount) && currentRetryCount > maxRetryCount) {
return
}
setTimeout(revalidate, timeout, opts)
}
const compare = dequal
// Default cache provider
const [cache, mutate] = initCache(new Map()) as [Cache, ScopedMutator]
export { cache, mutate, compare }
// Default config
export const defaultConfig: FullConfiguration = mergeObjects(
{
// events
onLoadingSlow: noop,
onSuccess: noop,
onError: noop,
onErrorRetry,
onDiscarded: noop,
// switches
revalidateOnFocus: true,
revalidateOnReconnect: true,
revalidateIfStale: true,
shouldRetryOnError: true,
// timeouts
errorRetryInterval: slowConnection ? 10000 : 5000,
focusThrottleInterval: 5 * 1000,
dedupingInterval: 2 * 1000,
loadingTimeout: slowConnection ? 5000 : 3000,
// providers
compare,
isPaused: () => false,
cache,
mutate,
fallback: {}
},
// use web preset by default
preset
)
================================================
FILE: src/_internal/utils/devtools.ts
================================================
import React from 'react'
import { isWindowDefined } from './helper'
// @ts-expect-error
const enableDevtools = isWindowDefined && window.__SWR_DEVTOOLS_USE__
export const use = enableDevtools
? // @ts-expect-error
window.__SWR_DEVTOOLS_USE__
: []
export const setupDevTools = () => {
if (enableDevtools) {
// @ts-expect-error
window.__SWR_DEVTOOLS_REACT__ = React
}
}
================================================
FILE: src/_internal/utils/env.ts
================================================
import React, { useEffect, useLayoutEffect } from 'react'
import { hasRequestAnimationFrame, isLegacyDeno, isWindowDefined } from './helper'
export const IS_REACT_LEGACY = !React.useId
export const IS_SERVER = !isWindowDefined || isLegacyDeno
// Polyfill requestAnimationFrame
export const rAF = (
f: (...args: any[]) => void
): number | ReturnType =>
hasRequestAnimationFrame()
? window['requestAnimationFrame'](f)
: setTimeout(f, 1)
// React currently throws a warning when using useLayoutEffect on the server.
// To get around it, we can conditionally useEffect on the server (no-op) and
// useLayoutEffect in the browser.
export const useIsomorphicLayoutEffect = IS_SERVER ? useEffect : useLayoutEffect
// This assignment is to extend the Navigator type to use effectiveType.
const navigatorConnection =
typeof navigator !== 'undefined' &&
(
navigator as Navigator & {
connection?: {
effectiveType: string
saveData: boolean
}
}
).connection
// Adjust the config based on slow connection status (<= 70Kbps).
export const slowConnection =
!IS_SERVER &&
navigatorConnection &&
(['slow-2g', '2g'].includes(navigatorConnection.effectiveType) ||
navigatorConnection.saveData)
================================================
FILE: src/_internal/utils/global-state.ts
================================================
import type { Cache, GlobalState } from '../types'
// Global state used to deduplicate requests and store listeners
export const SWRGlobalState = new WeakMap()
================================================
FILE: src/_internal/utils/hash.ts
================================================
import { OBJECT, isUndefined } from './shared'
// use WeakMap to store the object->key mapping
// so the objects can be garbage collected.
// WeakMap uses a hashtable under the hood, so the lookup
// complexity is almost O(1).
const table = new WeakMap