Mirror: The highly customizable and versatile GraphQL client with which you add on features like normalized caching as you grow.

Use getCurrentScope instead of getCurrentInstance (#3806)

Signed-off-by: Julien Hauseux <julien.hauseux@gmail.com>

Changed files
+96 -30
.changeset
packages
+5
.changeset/thick-roses-joke.md
···
+
---
+
'@urql/vue': major
+
---
+
+
Bump Vue to 3.2+ and replace getCurrentInstance with getCurrentScope
+1 -1
packages/vue-urql/package.json
···
},
"peerDependencies": {
"@urql/core": "^5.0.0",
-
"vue": "^2.7.0 || ^3.0.0"
+
"vue": "^3.2.0"
},
"dependencies": {
"@urql/core": "workspace:^5.2.0",
+64 -2
packages/vue-urql/src/useClient.test.ts
···
// @vitest-environment jsdom
import { expect, it, describe } from 'vitest';
-
import { defineComponent } from 'vue';
+
import { defineComponent, effectScope, h } from 'vue';
import { mount } from '@vue/test-utils';
import { Client } from '@urql/core';
import { useClient, provideClient } from './useClient';
-
describe('provideClient', () => {
+
describe('provideClient and useClient', () => {
it('provides client to current component instance', async () => {
const TestComponent = defineComponent({
setup() {
···
});
mount(TestComponent);
+
});
+
+
it('provides client to child components via provide/inject', async () => {
+
const ChildComponent = defineComponent({
+
setup() {
+
const client = useClient();
+
expect(client).toBeDefined();
+
return () => null;
+
},
+
});
+
+
const ParentComponent = defineComponent({
+
components: { ChildComponent },
+
setup() {
+
provideClient(
+
new Client({
+
url: 'test',
+
exchanges: [],
+
})
+
);
+
return () => h(ChildComponent);
+
},
+
});
+
+
mount(ParentComponent);
+
});
+
+
it('works in effect scopes outside components', () => {
+
const scope = effectScope();
+
+
scope.run(() => {
+
provideClient(
+
new Client({
+
url: 'test',
+
exchanges: [],
+
})
+
);
+
+
const client = useClient();
+
expect(client).toBeDefined();
+
});
+
});
+
+
it('throws error when no client is provided', () => {
+
expect(() => {
+
const TestComponent = defineComponent({
+
setup() {
+
// No provideClient called
+
useClient(); // Should throw
+
return null;
+
},
+
});
+
+
mount(TestComponent);
+
}).toThrow('No urql Client was provided');
+
});
+
+
it('throws error when called outside reactive context', () => {
+
expect(() => {
+
// Called outside any component or scope
+
useClient();
+
}).toThrow('reactive context');
});
});
+26 -27
packages/vue-urql/src/useClient.ts
···
-
import type { App, Ref } from 'vue';
-
import { getCurrentInstance, inject, provide, isRef, shallowRef } from 'vue';
+
import { type App, getCurrentScope, type Ref } from 'vue';
+
import { inject, provide, isRef, shallowRef } from 'vue';
import type { ClientOptions } from '@urql/core';
import { Client } from '@urql/core';
-
const clientsPerInstance = new WeakMap<{}, Ref<Client>>();
+
// WeakMap to store client instances as fallback when client is provided and used in the same component
+
const clientsPerScope = new WeakMap<{}, Ref<Client>>();
-
/** Provides a {@link Client} to a component’s children.
+
/** Provides a {@link Client} to a component and it’s children.
*
* @param opts - {@link ClientOptions}, a {@link Client}, or a reactive ref object of a `Client`.
*
···
*
* @example
* ```ts
-
* import { provideClient } from '@urql/vue';
-
* // All of `@urql/core` is also re-exported by `@urql/vue`:
-
* import { Client, cacheExchange, fetchExchange } from '@urql/core';
+
* <script setup>
+
* import { provideClient } from '@urql/vue';
+
* // All of `@urql/core` is also re-exported by `@urql/vue`:
+
* import { Client, cacheExchange, fetchExchange } from '@urql/core';
*
-
* export default {
-
* setup() {
-
* provideClient(new Client({
-
* url: 'https://API',
-
* exchanges: [cacheExchange, fetchExchange],
-
* }));
-
* },
-
* };
+
* provideClient(new Client({
+
* url: 'https://API',
+
* exchanges: [cacheExchange, fetchExchange],
+
* }));
+
* </script>
* ```
*/
export function provideClient(opts: ClientOptions | Client | Ref<Client>) {
···
client = opts;
}
-
const instance = getCurrentInstance();
-
if (instance) {
-
clientsPerInstance.set(instance, client);
+
const scope = getCurrentScope();
+
if (scope) {
+
clientsPerScope.set(scope, client);
}
provide('$urql', client);
···
/** Returns a provided reactive ref object of a {@link Client}.
*
* @remarks
-
* `useClient` may be called in Vue `setup` functions to retrieve a
-
* reactive rev object of a {@link Client} that’s previously been
+
* `useClient` may be called in a reactive context to retrieve a
+
* reactive ref object of a {@link Client} that’s previously been
* provided with {@link provideClient} in the current or a parent’s
* `setup` function.
*
* @throws
-
* In development, if `useClient` is called outside of a Vue `setup`
-
* function or no {@link Client} was provided, an error will be thrown.
+
* In development, if `useClient` is called outside of a reactive context
+
* or no {@link Client} was provided, an error will be thrown.
*/
export function useClient(): Ref<Client> {
-
const instance = getCurrentInstance();
-
if (process.env.NODE_ENV !== 'production' && !instance) {
+
const scope = getCurrentScope();
+
if (process.env.NODE_ENV !== 'production' && !scope) {
throw new Error(
-
'use* functions may only be called during the `setup()` or other lifecycle hooks.'
+
'use* function must be called within a reactive context (component setup, composable, or effect scope).'
);
}
let client = inject('$urql') as Ref<Client> | undefined;
-
if (!client && instance) {
-
client = clientsPerInstance.get(instance);
+
if (!client) {
+
client = clientsPerScope.get(scope!);
}
if (process.env.NODE_ENV !== 'production' && !client) {