Skip to content

Commit 4bbfe2a

Browse files
feat(broadcastQueryClient): experimental support for tab/window syncing (TanStack#1793)
* feat(subscriptions): add event types and metadata to subscriptions * feat(broadcastQueryClient): experimental support for tab/window syncing * better types, update tests * feat(broadcastQueryClient): experimental support for tab/window syncing * rebase and update * use transactions * update docs * Update broadcastQueryClient.md * Update index.ts
1 parent 9fad892 commit 4bbfe2a

File tree

9 files changed

+230
-4
lines changed

9 files changed

+230
-4
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"internal": true,
3+
"main": "../lib/broadcastQueryClient-experimental/index.js",
4+
"module": "../es/broadcastQueryClient-experimental/index.js",
5+
"types": "../types/broadcastQueryClient-experimental/index.d.ts"
6+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
---
2+
id: broadcastQueryClient
3+
title: broadcastQueryClient (Experimental)
4+
---
5+
6+
> VERY IMPORTANT: This utility is currently in an experimental stage. This means that breaking changes will happen in minor AND patch releases. Use at your own risk. If you choose to rely on this in production in an experimental stage, please lock your version to a patch-level version to avoid unexpected breakages.
7+
8+
`broadcastQueryClient` is a utility for broadcasting and syncing the state of your queryClient between browser tabs/windows with the same origin.
9+
10+
## Installation
11+
12+
This utility comes packaged with `react-query` and is available under the `react-query/broadcastQueryClient-experimental` import.
13+
14+
## Usage
15+
16+
Import the `broadcastQueryClient` function, and pass it your `QueryClient` instance, and optionally, set a `broadcastChannel`.
17+
18+
```ts
19+
import { broadcastQueryClient } from 'react-query/broadcastQueryClient-experimental'
20+
21+
const queryClient = new QueryClient()
22+
23+
broadcastQueryClient({
24+
queryClient,
25+
broadcastChannel: 'my-app',
26+
})
27+
```
28+
29+
## API
30+
31+
### `broadcastQueryClient`
32+
33+
Pass this function a `QueryClient` instance and optionally, a `broadcastChannel`.
34+
35+
```ts
36+
broadcastQueryClient({ queryClient, broadcastChannel })
37+
```
38+
39+
### `Options`
40+
41+
An object of options:
42+
43+
```ts
44+
interface broadcastQueryClient {
45+
/** The QueryClient to sync */
46+
queryClient: QueryClient
47+
/** This is the unique channel name that will be used
48+
* to communicate between tabs and windows */
49+
broadcastChannel?: string
50+
}
51+
```
52+
53+
The default options are:
54+
55+
```ts
56+
{
57+
broadcastChannel = 'react-query',
58+
}
59+
```

examples/basic/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
},
99
"dependencies": {
1010
"axios": "^0.21.1",
11+
"broadcast-channel": "^3.4.1",
1112
"react": "^16.8.6",
1213
"react-dom": "^16.8.6",
1314
"react-query": "^3.5.0",

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,13 +50,15 @@
5050
"devtools",
5151
"persistQueryClient-experimental",
5252
"createLocalStoragePersistor-experimental",
53+
"broadcastQueryClient-experimental",
5354
"lib",
5455
"react",
5556
"scripts",
5657
"types"
5758
],
5859
"dependencies": {
5960
"@babel/runtime": "^7.5.5",
61+
"broadcast-channel": "^3.4.1",
6062
"match-sorter": "^6.0.2"
6163
},
6264
"peerDependencies": {

rollup.config.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@ const inputSrcs = [
2929
'ReactQueryCreateLocalStoragePersistorExperimental',
3030
'createLocalStoragePersistor-experimental',
3131
],
32+
[
33+
'src/broadcastQueryClient-experimental/index.ts',
34+
'ReactQueryBroadcastQueryClientExperimental',
35+
'broadcastQueryClient-experimental',
36+
],
3237
]
3338

3439
const extensions = ['.js', '.jsx', '.es6', '.es', '.mjs', '.ts', '.tsx']
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import { BroadcastChannel } from 'broadcast-channel'
2+
import { QueryClient } from '../core'
3+
4+
interface BroadcastQueryClientOptions {
5+
queryClient: QueryClient
6+
broadcastChannel: string
7+
}
8+
9+
export function broadcastQueryClient({
10+
queryClient,
11+
broadcastChannel = 'react-query',
12+
}: BroadcastQueryClientOptions) {
13+
let transaction = false
14+
const tx = (cb: () => void) => {
15+
transaction = true
16+
cb()
17+
transaction = false
18+
}
19+
20+
const channel = new BroadcastChannel(broadcastChannel, {
21+
webWorkerSupport: false,
22+
})
23+
24+
const queryCache = queryClient.getQueryCache()
25+
26+
queryClient.getQueryCache().subscribe(queryEvent => {
27+
if (transaction || !queryEvent?.query) {
28+
return
29+
}
30+
31+
const {
32+
query: { queryHash, queryKey, state },
33+
} = queryEvent
34+
35+
if (
36+
queryEvent.type === 'queryUpdated' &&
37+
queryEvent.action?.type === 'success'
38+
) {
39+
channel.postMessage({
40+
type: 'queryUpdated',
41+
queryHash,
42+
queryKey,
43+
state,
44+
})
45+
}
46+
47+
if (queryEvent.type === 'queryRemoved') {
48+
channel.postMessage({
49+
type: 'queryRemoved',
50+
queryHash,
51+
queryKey,
52+
})
53+
}
54+
})
55+
56+
channel.onmessage = action => {
57+
if (!action?.type) {
58+
return
59+
}
60+
61+
tx(() => {
62+
const { type, queryHash, queryKey, state } = action
63+
64+
if (type === 'queryUpdated') {
65+
const query = queryCache.get(queryHash)
66+
67+
if (query) {
68+
query.setState(state)
69+
return
70+
}
71+
72+
queryCache.build(
73+
queryClient,
74+
{
75+
queryKey,
76+
queryHash,
77+
},
78+
state
79+
)
80+
} else if (type === 'queryRemoved') {
81+
const query = queryCache.get(queryHash)
82+
83+
if (query) {
84+
queryCache.remove(query)
85+
}
86+
}
87+
})
88+
}
89+
}

src/core/query.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ interface ContinueAction {
106106
interface SetStateAction<TData, TError> {
107107
type: 'setState'
108108
state: QueryState<TData, TError>
109+
setStateOptions?: SetStateOptions
109110
}
110111

111112
export type Action<TData, TError> =
@@ -118,6 +119,10 @@ export type Action<TData, TError> =
118119
| SetStateAction<TData, TError>
119120
| SuccessAction<TData>
120121

122+
export interface SetStateOptions {
123+
meta?: any
124+
}
125+
121126
// CLASS
122127

123128
export class Query<
@@ -216,8 +221,11 @@ export class Query<
216221
return data
217222
}
218223

219-
setState(state: QueryState<TData, TError>): void {
220-
this.dispatch({ type: 'setState', state })
224+
setState(
225+
state: QueryState<TData, TError>,
226+
setStateOptions?: SetStateOptions
227+
): void {
228+
this.dispatch({ type: 'setState', state, setStateOptions })
221229
}
222230

223231
cancel(options?: CancelOptions): Promise<void> {

tsconfig.types.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111
"./src/hydration/index.ts",
1212
"./src/devtools/index.ts",
1313
"./src/persistQueryClient-experimental/index.ts",
14-
"./src/createLocalStoragePersistor-experimental/index.ts"
14+
"./src/createLocalStoragePersistor-experimental/index.ts",
15+
"./src/broadcastQueryClient-experimental/index.ts"
1516
],
1617
"exclude": ["./src/**/*"]
1718
}

yarn.lock

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1944,6 +1944,13 @@
19441944
dependencies:
19451945
regenerator-runtime "^0.13.4"
19461946

1947+
"@babel/runtime@^7.6.2", "@babel/runtime@^7.7.2":
1948+
version "7.12.13"
1949+
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.12.13.tgz#0a21452352b02542db0ffb928ac2d3ca7cb6d66d"
1950+
integrity sha512-8+3UMPBrjFa/6TtKi/7sehPKqfAm4g6K+YQjyyFOLUTxzOngcRZTlAVY8sc2CORJYqdHQY8gRPHmn+qo15rCBw==
1951+
dependencies:
1952+
regenerator-runtime "^0.13.4"
1953+
19471954
"@babel/runtime@^7.8.4":
19481955
version "7.10.2"
19491956
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.10.2.tgz#d103f21f2602497d38348a32e008637d506db839"
@@ -3088,6 +3095,11 @@ bcrypt-pbkdf@^1.0.0:
30883095
dependencies:
30893096
tweetnacl "^0.14.3"
30903097

3098+
big-integer@^1.6.16:
3099+
version "1.6.48"
3100+
resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.48.tgz#8fd88bd1632cba4a1c8c3e3d7159f08bb95b4b9e"
3101+
integrity sha512-j51egjPa7/i+RdiRuJbPdJ2FIUYYPhvYLjzoYbcMMm62ooO6F94fETG4MTs46zPAF9Brs04OajboA/qTGuz78w==
3102+
30913103
binary-extensions@^1.0.0:
30923104
version "1.13.1"
30933105
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.1.tgz#598afe54755b2868a5330d2aff9d4ebb53209b65"
@@ -3136,6 +3148,19 @@ braces@^3.0.1:
31363148
dependencies:
31373149
fill-range "^7.0.1"
31383150

3151+
broadcast-channel@^3.4.1:
3152+
version "3.4.1"
3153+
resolved "https://registry.yarnpkg.com/broadcast-channel/-/broadcast-channel-3.4.1.tgz#65b63068d0a5216026a19905c9b2d5e9adf0928a"
3154+
integrity sha512-VXYivSkuBeQY+pL5hNQQNvBdKKQINBAROm4G8lAbWQfOZ7Yn4TMcgLNlJyEqlkxy5G8JJBsI3VJ1u8FUTOROcg==
3155+
dependencies:
3156+
"@babel/runtime" "^7.7.2"
3157+
detect-node "^2.0.4"
3158+
js-sha3 "0.8.0"
3159+
microseconds "0.2.0"
3160+
nano-time "1.0.0"
3161+
rimraf "3.0.2"
3162+
unload "2.2.0"
3163+
31393164
brotli-size@^4.0.0:
31403165
version "4.0.0"
31413166
resolved "https://registry.yarnpkg.com/brotli-size/-/brotli-size-4.0.0.tgz#a05ee3faad3c0e700a2f2da826ba6b4d76e69e5e"
@@ -3652,6 +3677,11 @@ detect-newline@^3.0.0:
36523677
resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651"
36533678
integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==
36543679

3680+
detect-node@^2.0.4:
3681+
version "2.0.4"
3682+
resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.0.4.tgz#014ee8f8f669c5c58023da64b8179c083a28c46c"
3683+
integrity sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw==
3684+
36553685
diff-sequences@^25.2.6:
36563686
version "25.2.6"
36573687
resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-25.2.6.tgz#5f467c00edd35352b7bca46d7927d60e687a76dd"
@@ -5468,6 +5498,11 @@ jest@^26.0.1:
54685498
import-local "^3.0.2"
54695499
jest-cli "^26.0.1"
54705500

5501+
js-sha3@0.8.0:
5502+
version "0.8.0"
5503+
resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.8.0.tgz#b9b7a5da73afad7dedd0f8c463954cbde6818840"
5504+
integrity sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==
5505+
54715506
"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
54725507
version "4.0.0"
54735508
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
@@ -5863,6 +5898,11 @@ micromatch@^4.0.2:
58635898
braces "^3.0.1"
58645899
picomatch "^2.0.5"
58655900

5901+
microseconds@0.2.0:
5902+
version "0.2.0"
5903+
resolved "https://registry.yarnpkg.com/microseconds/-/microseconds-0.2.0.tgz#233b25f50c62a65d861f978a4a4f8ec18797dc39"
5904+
integrity sha512-n7DHHMjR1avBbSpsTBj6fmMGh2AGrifVV4e+WYc3Q9lO+xnSZ3NyhcBND3vzzatt05LFhoKFRxrIyklmLlUtyA==
5905+
58665906
mime-db@1.42.0:
58675907
version "1.42.0"
58685908
resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.42.0.tgz#3e252907b4c7adb906597b4b65636272cf9e7bac"
@@ -5945,6 +5985,13 @@ nan@^2.12.1:
59455985
resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.1.tgz#d7be34dfa3105b91494c3147089315eff8874b01"
59465986
integrity sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw==
59475987

5988+
nano-time@1.0.0:
5989+
version "1.0.0"
5990+
resolved "https://registry.yarnpkg.com/nano-time/-/nano-time-1.0.0.tgz#b0554f69ad89e22d0907f7a12b0993a5d96137ef"
5991+
integrity sha1-sFVPaa2J4i0JB/ehKwmTpdlhN+8=
5992+
dependencies:
5993+
big-integer "^1.6.16"
5994+
59485995
nanoid@^3.0.1:
59495996
version "3.1.3"
59505997
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.3.tgz#b2bcfcfda4b4d6838bc22a0c8dd3c0a17a204c20"
@@ -6823,7 +6870,7 @@ rimraf@2.6.3:
68236870
dependencies:
68246871
glob "^7.1.3"
68256872

6826-
rimraf@^3.0.0, rimraf@^3.0.2:
6873+
rimraf@3.0.2, rimraf@^3.0.0, rimraf@^3.0.2:
68276874
version "3.0.2"
68286875
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a"
68296876
integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==
@@ -7755,6 +7802,14 @@ universalify@^0.1.0:
77557802
resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66"
77567803
integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==
77577804

7805+
unload@2.2.0:
7806+
version "2.2.0"
7807+
resolved "https://registry.yarnpkg.com/unload/-/unload-2.2.0.tgz#ccc88fdcad345faa06a92039ec0f80b488880ef7"
7808+
integrity sha512-B60uB5TNBLtN6/LsgAf3udH9saB5p7gqJwcFfbOEZ8BcBHnGwCf6G/TGiEqkRAxX7zAFIUtzdrXQSdL3Q/wqNA==
7809+
dependencies:
7810+
"@babel/runtime" "^7.6.2"
7811+
detect-node "^2.0.4"
7812+
77587813
unquote@~1.1.1:
77597814
version "1.1.1"
77607815
resolved "https://registry.yarnpkg.com/unquote/-/unquote-1.1.1.tgz#8fded7324ec6e88a0ff8b905e7c098cdc086d544"

0 commit comments

Comments
 (0)