Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Subscription mode #1263

Merged
merged 31 commits into from
Feb 24, 2023
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
cbf8752
update subscribe with middleware
huozhi Jul 2, 2021
af89adb
fix lint & test
huozhi Jul 19, 2021
7283270
update typing, fix test and lint
huozhi Feb 1, 2022
bfa9056
refactor callback, rename, use refs for callbacks
huozhi Feb 3, 2022
510524a
Delay unmount
huozhi Feb 3, 2022
abfa159
Merge branch 'main' into subscribe
huozhi Feb 3, 2022
8ed9d72
Merge branch 'main' into subscribe
huozhi Apr 5, 2022
8f48cce
Merge branch 'main' into subscribe
huozhi Apr 12, 2022
8cfc3f5
no swr destruction and add getters, revert pkg.json
huozhi Apr 16, 2022
c023e89
callback -> next
huozhi Apr 17, 2022
2f33952
rename fix types and update test
huozhi Apr 17, 2022
138ede4
change exports to unstable_subscription
huozhi Apr 19, 2022
350f022
Merge branch 'main' into subscribe
huozhi May 17, 2022
7ad1935
manage subs with useESE
huozhi May 17, 2022
beaf868
use serialize
huozhi May 18, 2022
0e873eb
merge canary
huozhi Sep 22, 2022
81e2ab1
Merge branch 'main' into subscribe
huozhi Jan 23, 2023
4753152
fix lint
huozhi Jan 23, 2023
401738c
use cache helper to update error
huozhi Jan 24, 2023
6aed4ea
update exports path
huozhi Jan 26, 2023
ca2668b
fix script
huozhi Feb 1, 2023
c0ab66e
Merge branch 'main' into subscribe
huozhi Feb 1, 2023
8276703
pub check
huozhi Feb 1, 2023
c21aa98
add exp jsdoc
huozhi Feb 24, 2023
1c2b974
use cache-scoped storage
shuding Feb 24, 2023
5656fef
use prefixed key; add more tests
shuding Feb 24, 2023
12e3111
fix type
shuding Feb 24, 2023
0c04bfd
remove subscriber ref
shuding Feb 24, 2023
73ffb1c
rename
shuding Feb 24, 2023
7963ae8
fix lint
huozhi Feb 24, 2023
54dfaee
Merge branch 'main' into subscribe
huozhi Feb 24, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions _internal/utils/mutate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ export async function internalMutate<Data>(
typeof _opts === 'boolean' ? { revalidate: _opts } : _opts || {}
)

console.log('opts', options)

shuding marked this conversation as resolved.
Show resolved Hide resolved
let populateCache = options.populateCache

const rollbackOnErrorOption = options.rollbackOnError
Expand Down
1 change: 1 addition & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ module.exports = {
'^swr$': '<rootDir>/core/index.ts',
'^swr/infinite$': '<rootDir>/infinite/index.ts',
'^swr/immutable$': '<rootDir>/immutable/index.ts',
'^swr/subscription$': '<rootDir>/subscription/index.ts',
'^swr/mutation$': '<rootDir>/mutation/index.ts',
'^swr/_internal$': '<rootDir>/_internal/index.ts'
},
Expand Down
12 changes: 10 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@
"module": "./immutable/dist/index.esm.js",
"require": "./immutable/dist/index.js"
},
"./subscription": {
"types": "./subscription/dist/index.d.ts",
"import": "./subscription/dist/index.mjs",
"module": "./subscription/dist/index.esm.js",
"require": "./subscription/dist/index.js"
},
"./mutation": {
"types": "./mutation/dist/mutation/index.d.ts",
"import": "./mutation/dist/index.mjs",
Expand All @@ -53,11 +59,13 @@
"immutable/dist/**/*.{js,d.ts,mjs}",
"mutation/dist/**/*.{js,d.ts,mjs}",
"_internal/dist/**/*.{js,d.ts,mjs}",
"subscription/dist/*.{js,d.ts,mjs}",
"core/package.json",
"infinite/package.json",
"immutable/package.json",
"mutation/package.json",
"_internal/package.json"
"_internal/package.json",
"subscription/package.json"
],
"repository": "github:vercel/swr",
"homepage": "https://swr.vercel.app",
Expand All @@ -68,7 +76,7 @@
"csb:build": "pnpm build",
"clean": "pnpm -r run clean && rimraf playwright-report test-result",
"watch": "pnpm -r run watch",
"build": "pnpm build-package _internal && pnpm build-package core && pnpm build-package infinite && pnpm build-package immutable && pnpm build-package mutation",
"build": "pnpm build-package _internal && pnpm build-package core && pnpm build-package infinite && pnpm build-package immutable && pnpm build-package mutation && pnpm build-package subscription",
"build:e2e": "pnpm next build e2e/site",
"build-package": "bunchee index.ts --cwd",
"types:check": "pnpm -r run types:check",
Expand Down
92 changes: 92 additions & 0 deletions subscription/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import type { Key, SWRHook, Middleware, SWRConfiguration, SWRConfig } from 'swr'
import { useRef } from 'react'
import useSWR from 'swr'
import {
withMiddleware,
serialize,
useIsomorphicLayoutEffect,
createCacheHelper
} from 'swr/_internal'

export type SWRSubscription<Data = any, Error = any> = (
key: Key,
{ next }: { next: (err?: Error, data?: Data) => void }
) => void

export type SWRSubscriptionResponse<Data = any, Error = any> = {
data?: Data
error?: Error
}

export type SWRSubscriptionHook<Data = any, Error = any> = (
key: Key,
subscribe: SWRSubscription<Data, Error>,
config?: SWRConfiguration
) => SWRSubscriptionResponse<Data, Error>

const subscriptions = new Map<Key, number>()
const disposers = new Map()

export const subscription = (<Data, Error>(useSWRNext: SWRHook) =>
(
_key: Key,
subscribe: SWRSubscription<Data, Error>,
config: SWRConfiguration & typeof SWRConfig.defaultValue
): SWRSubscriptionResponse<Data, Error> => {
const [key] = serialize(_key)
const swr = useSWRNext(key, null, config)
const subscribeRef = useRef(subscribe)

huozhi marked this conversation as resolved.
Show resolved Hide resolved
useIsomorphicLayoutEffect(() => {
subscribeRef.current = subscribe
})

const { cache } = config
const [, set] = createCacheHelper<Data>(cache, key)

useIsomorphicLayoutEffect(() => {
subscriptions.set(key, (subscriptions.get(key) || 0) + 1)
huozhi marked this conversation as resolved.
Show resolved Hide resolved

const onData = (val?: Data) => swr.mutate(val, false)
const onError = async (err: any) => set({ error: err })
shuding marked this conversation as resolved.
Show resolved Hide resolved

const next = (err_?: any, data_?: Data) => {
shuding marked this conversation as resolved.
Show resolved Hide resolved
console.log('next:_err', err_)
shuding marked this conversation as resolved.
Show resolved Hide resolved
if (err_) onError(err_)
shuding marked this conversation as resolved.
Show resolved Hide resolved
else onData(data_)
}

if (subscriptions.get(key) === 1) {
const dispose = subscribeRef.current(key, { next })
disposers.set(key, dispose)
}
return () => {
// Prevent frequent unsubscribe caused by unmount
setTimeout(() => {
const count = subscriptions.get(key) || 1
subscriptions.set(key, count - 1)
// Dispose if it's last one
if (count === 1) {
disposers.get(key)()
}
})
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [key])

return {
get data() {
return swr.data
},
get error() {
return swr.error
}
}
}) as unknown as Middleware

const useSWRSubscription = withMiddleware(
useSWR,
subscription
) as SWRSubscriptionHook

export default useSWRSubscription
17 changes: 17 additions & 0 deletions subscription/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"main": "./dist/index.js",
"module": "./dist/index.esm.js",
"types": "./dist/index.d.ts",
"exports": "./dist/index.mjs",
"private": true,
"scripts": {
"watch": "bunchee index.ts -w",
"build": "bunchee index.ts",
"types:check": "tsc --noEmit",
"clean": "rimraf dist"
},
"peerDependencies": {
"swr": "*",
"react": "*"
}
}
9 changes: 9 additions & 0 deletions subscription/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "..",
},
"include": [".", "../src"],
"exclude": ["./dist"]
}
47 changes: 47 additions & 0 deletions test/use-swr-subscription.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import React from 'react'
import { act, screen } from '@testing-library/react'
import { sleep, renderWithConfig } from './utils'
import useSWRSubscription from 'swr/subscription'

describe('useSWRSubscription', () => {
it('should update state when fetcher is a subscription', async () => {
const swrKey = 'sub-0'
let intervalId
let res = 0
function subscribe(key, { next }) {
intervalId = setInterval(() => {
if (res === 3) {
const err = new Error(key + 'error')
next(err)
} else {
next(undefined, key + res)
}
res++
}, 100)

return () => {}
}

function Page() {
const { data, error } = useSWRSubscription(swrKey, subscribe, {
fallbackData: 'fallback'
})
return <div>{error ? error.message : data}</div>
}

renderWithConfig(<Page />)
await act(() => sleep(10))
screen.getByText(`fallback`)
await act(() => sleep(100))
screen.getByText(`${swrKey}0`)
await act(() => sleep(100))
screen.getByText(`${swrKey}1`)
await act(() => sleep(100))
screen.getByText(`${swrKey}2`)
await act(() => sleep(100))
screen.getByText(`${swrKey}error`)
clearInterval(intervalId)
await sleep(100)
screen.getByText(`${swrKey}error`)
})
})
3 changes: 2 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@
"swr/infinite": ["./infinite/index.ts"],
"swr/immutable": ["./immutable/index.ts"],
"swr/mutation": ["./mutation/index.ts"],
"swr/_internal": ["./_internal/index.ts"]
"swr/_internal": ["./_internal/index.ts"],
"swr/subscription": ["subscription/index.ts"],
},
"incremental": true
},
Expand Down