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 15 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
1 change: 1 addition & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ module.exports = {
'^swr$': '<rootDir>/core/index.ts',
'^swr/infinite$': '<rootDir>/infinite/index.ts',
'^swr/immutable$': '<rootDir>/immutable/index.ts',
'^swr/unstable_subscription$': '<rootDir>/subscription/index.ts',
'^swr/mutation$': '<rootDir>/mutation/index.ts',
'^swr/_internal$': '<rootDir>/_internal/index.ts'
},
Expand Down
14 changes: 12 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@
"require": "./immutable/dist/index.js",
"types": "./immutable/dist/immutable/index.d.ts"
},
"./unstable_subscription": {
"import": "./subscription/dist/index.mjs",
"module": "./subscription/dist/index.esm.js",
"require": "./subscription/dist/index.js",
"types": "./subscription/dist/subscription/index.d.ts"
},
"./mutation": {
"import": "./mutation/dist/index.mjs",
"module": "./mutation/dist/index.esm.js",
Expand All @@ -51,6 +57,8 @@
"infinite/dist/**",
"immutable/dist/**",
"mutation/dist/**",
"subscription/dist/**",
"subscription/package.json",
"_internal/dist/**",
"core/dist/package.json",
"infinite/package.json",
Expand All @@ -64,16 +72,18 @@
"license": "MIT",
"scripts": {
"clean": "rimraf core/dist infinite/dist immutable/dist mutation/dist",
"build": "yarn build:internal && yarn build:core && yarn build:infinite && yarn build:immutable && yarn build:mutation",
"watch": "npm-run-all -p watch:core watch:infinite watch:immutable watch:mutation",
"build": "yarn build:internal && yarn build:core && yarn build:infinite && yarn build:immutable && yarn build:mutation && yarn build:subscription",
"watch": "npm-run-all -p watch:core watch:infinite watch:immutable watch:mutation watch:subscription",
"watch:core": "yarn build:core -w",
"watch:infinite": "yarn build:infinite -w",
"watch:immutable": "yarn build:immutable -w",
"watch:mutation": "yarn build:mutation -w",
"watch:subscription": "yarn build:subscription -w",
"build:core": "bunchee index.ts --cwd core --no-sourcemap",
"build:infinite": "bunchee index.ts --cwd infinite --no-sourcemap",
"build:immutable": "bunchee index.ts --cwd immutable --no-sourcemap",
"build:mutation": "bunchee index.ts --cwd mutation --no-sourcemap",
"build:subscription": "bunchee index.ts --cwd subscription --no-sourcemap",
"build:internal": "bunchee index.ts --cwd _internal --no-sourcemap",
"prepublishOnly": "yarn clean && yarn build",
"publish-beta": "yarn publish --tag beta",
Expand Down
100 changes: 100 additions & 0 deletions subscription/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import { useRef } from 'react'
import useSWR from 'swr'
import {
withMiddleware,
Key,
SWRHook,
Middleware,
serialize,
SWRConfiguration,
useIsomorphicLayoutEffect
} 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
): 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
})

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) => {
// Avoid thrown errors from `mutate`
// eslint-disable-next-line no-empty
try {
await swr.mutate(() => {
throw err
}, false)
} catch (_) {
/* eslint-disable-line no-empty */
}
}

const next = (_err?: any, _data?: Data) => {
if (_err) onError(_err)
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
12 changes: 12 additions & 0 deletions subscription/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"name": "swr-subscription",
"version": "0.0.1",
"main": "./dist/index.js",
"module": "./dist/index.esm.js",
"types": "./dist/subscription",
"exports": "./dist/index.mjs",
"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/unstable_subscription'

describe('useSWRSubscription', () => {
it('should update state when fetcher is a subscription', async () => {
const key = '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(key, 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(`${key}0`)
await act(() => sleep(100))
screen.getByText(`${key}1`)
await act(() => sleep(100))
screen.getByText(`${key}2`)
await act(() => sleep(100))
screen.getByText(`${key}error`)
clearInterval(intervalId)
await sleep(100)
screen.getByText(`${key}error`)
})
})
2 changes: 1 addition & 1 deletion tsconfig.check.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@
"compilerOptions": {
"rootDir": "."
},
"include": ["src", "immutable", "infinite", "mutation"]
"include": ["src", "immutable", "infinite", "mutation", "subscription"]
}
3 changes: 2 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@
"swr": ["./core/index.ts"],
"swr/infinite": ["./infinite/index.ts"],
"swr/immutable": ["./immutable/index.ts"],
"swr/mutation": ["./mutation/index.ts"]
"swr/mutation": ["./mutation/index.ts"],
"swr/unstable_subscription": ["subscription/index.ts"]
},
"typeRoots": ["./node_modules/@types"],
"incremental": true
Expand Down