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
feat: enable useQuery etc function outside of setup and outside component #156
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
First, I'm terribly sorry about the delay in reviewing this. This is a great addition and a very welcome contribution.
Awesome work! and the tests are passing as well, but I would love to discuss the requests I have and also add some tests to see if this works as intended outside the setup
.
So we could add tests for it in those situations:
- After previous
await
insetup
- Outside the execution of
setup
(in a function or whatever)
packages/villus/src/client.ts
Outdated
export let activeClient: Client | undefined; | ||
|
||
/** | ||
* Sets or unsets the active client | ||
* | ||
* @param client - villus client instance | ||
*/ | ||
export const setActiveClient = (client: Client | undefined) => (activeClient = client); | ||
|
||
/** | ||
* Get the currently active client if there is any. | ||
*/ | ||
export const getActiveClient = () => (getCurrentInstance() && inject(VILLUS_CLIENT)) || activeClient; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should leave out the inject
logic from here and instead focus on the activeClient instance getter and setter in isolation. We can move the injection to the injectWithSelf
call.
export let activeClient: Client | undefined; | |
/** | |
* Sets or unsets the active client | |
* | |
* @param client - villus client instance | |
*/ | |
export const setActiveClient = (client: Client | undefined) => (activeClient = client); | |
/** | |
* Get the currently active client if there is any. | |
*/ | |
export const getActiveClient = () => (getCurrentInstance() && inject(VILLUS_CLIENT)) || activeClient; | |
let activeClient: Client | undefined; | |
/** | |
* Sets or unsets the active client | |
* | |
* @param client - villus client instance | |
*/ | |
export const setActiveClient = (client: Client | undefined) => (activeClient = client); | |
/** | |
* Get the currently active client if there is any. | |
*/ | |
export const getActiveClient = () => activeClient; |
Note that since we have a reliable getter now we can avoid exporting the active client instance.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I worry about that if we getActiveClient, we can not assure injectWithSelf (resolveClient now) will be called first. so I write like below.
export const getActiveClient = () => (getCurrentInstance() && inject(VILLUS_CLIENT)) || activeClient;
I also think if we can unite the inject to getActiveClient, just like:
export function getActiveClient(manualClient?: Client): Client {
if (manualClient) {
return manualClient
}
const vm = getCurrentInstance() as any;
let client = vm && inject(VILLUS_CLIENT);
if (client) {
setActiveClient(client);
} else {
client = activeClient;
}
if (client === null || client === undefined) {
throw new Error('Cannot detect villus Client, did you forget to call `useClient`? or you can pass a specific client to manualClient param. more details please read the documentation.');
}
return client;
}
what do you think? If you also think it is reasonable, I will change it and have a check where use injectWithSelf.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
By the way, I think it may be better to modify the error message, because I am not good at english, I just add some words after. you can make it, then I will add it to these code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I worry about that if we getActiveClient, we can not assure resolveClient will be called first
Yes, that is a valid concern but getActiveClient
is only used inside resolveClient
. So there is no way it can get called before resolveClient
.
I also think if we can unite the inject to getActiveClient
Yep I see you did that in resolveClient
now, it doesn't matter really. We can either say getActiveClient
resolves the client or resolveClient
is the one to do that. Not a big difference and I think the current implementation is fine 👍
By the way, I think it may be better to modify the error message
Sure, what do you think of this:
Cannot detect villus Client, did you forget to call `useClient`? Alternatively, you can explicitly pass a client as the `manualClient` argument.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So there is no way it can get called before resolveClient.
Yes, there is no problem now, just in case someone will contribute to the code, they will not know the logic order at first (it is implicit). and worry we will forget it maybe extends code someday.
Not a big difference and I think the current implementation is fine
Agree that's fine now, so let's make it now
Cannot detect villus Client, did you forget to call
useClient
? Alternatively, you can explicitly pass a client as themanualClient
argument.
Clear and understandable. I will add it.
|
||
const injection = inject(symbol, vm?.provides?.[symbol as any]); | ||
if (injection === null || injection === undefined) { | ||
if (client) setActiveClient(client); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could be dangerous to set here. Assuming either useClient
or createClient
were called anywhere previously this would make this redundant, right? Unless you are mitigating something I'm not aware of.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I do have this worry at first, I think if we just create one client, it is not a problem. If we can create multi Client, due to the setActiveClient comes form current instance(If we can get vm), so setting it to default client is not a problem(I think), if who want use custom client (diff from vm), they should set a manualClient param ( this is why I want add a manual param to extend multi client.)
Maybe it's ok for one client, but you are right, If we use multi client, the order of setActiveClient anywhere may cause some error, I need some time to check it, then update this later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have some info, due to the solution comes from pinia, I found that we only create pinia instance once, then defineStore and then useTheDefinedStore. But when we use multi villus client, we may create multi times. and the useQuery function is not depends on client. If the setActiveClient order is not managed, it may get unexpected response.
I think when we use multi villusClient, It should call useQuery to offer a specific client ( the manualClient param.), or we can suggest user to define customer useQuery in docs, just like (it is an easy and more reliable ):
export function useCustomClientQuery (opt: QueryOptions) {
return useQuery(opt, customClient)
}
or villus can also have define function to extend more abilities.
by the way, I think when we can get client from current instance, set it to default and use it to query is reasonable. Developers should aware of which client provided in current instance. if not , pass a specific client is reliable (recommended). I think we can make it more clear in docs.
Above all is my simple opinion. do you have any other good idea?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So what I'm thinking is, if we set the active client there it will mess up when the user uses useQuery
or useMutation
when it is both outside setup
and they have multiple clients. It will only work for them if they expect the last created client to be injected.
If we don't do that, the same problem still occurs. Its just we only set it once and that's it. If they use a different client and expect that to be used outside the setup
it will mess up then.
So regardless of our choices here this would cause issues for people using multiple clients.
I'm willing to argue that using multiple clients at the same app isn't common and usually people stitch schemas on the backend, so this might be fine to leave as is.
Like you said the manualClient
option would solve it for those users in that case, which can be documented to make sure they don't fall into that trap.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
when it is both outside setup and they have multiple clients. It will only work for them if they expect the last created client to be injected.
It will use last currentInstance used client to execute query, because we may set activeClient in last currentInstance (resolveClient function). If we don't set, villus will execute with last created client. both will have some risk (if use multi client). I think last currentInstance client maybe more probability which user want choose.
So regardless of our choices here this would cause issues for people using multiple clients.
I do have the worry. but I have no more good idea now. so let's add it todo. and make a caution in documentation first to remind multi client user. Maybe someday new idea will come.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
By the way, because we will call createClient in useClient function. if we useClient before query, the activeClient will be set to the right one. So I think some one can useClient first as before (Maybe).
Can this PR also add documentation for the usage? |
Co-authored-by: Abdelrahman Awad <logaretm1@Gmail.com>
Co-authored-by: Abdelrahman Awad <logaretm1@Gmail.com>
Co-authored-by: Abdelrahman Awad <logaretm1@Gmail.com>
Co-authored-by: Abdelrahman Awad <logaretm1@Gmail.com>
As you can see, I am not good at english 😄 , I do have think adding doc commit before, but maybe it exceed my writing english ability. So I think it would be better let you @logaretm add it. Your writing documentation is clear and easy to understand (Wonderful and thanks) . If you need some help, please let me know. |
Last thing needed I think is to change |
Yes, I am waiting for your opinion before, and then I will change it ASAP. |
- add resolveClient symbol argument back, in case extending someday. - modify error msg.
Codecov Report
@@ Coverage Diff @@
## main #156 +/- ##
==========================================
+ Coverage 94.78% 94.88% +0.09%
==========================================
Files 26 26
Lines 537 547 +10
Branches 99 107 +8
==========================================
+ Hits 509 519 +10
Misses 28 28
Continue to review full report at Codecov.
|
@GavinXue Looking good 👍 there is a conflict though with the Other than that, I can add the documentation into this PR and a couple of tests and we can then merge it! |
@logaretm Is it okay now? I am sorry, I am not familiar with resolving conflict. |
Yep, all good. I will work on the documentation later tonight and tag a new release with this. Thank you so much for this contribution 👍 I'm happy to merge now if you are. |
@logaretm sure, thanks. I am glad this is useful. By the way, I found that maybe the documentation will suggest to use getActiveClient #157 (comment). I think we should notice the getActiveClient function will not getCurrentInstance client first (it always get last active client, it has risk in some cases). and we have no error message if there is no client (I'm not sure if needed). Considering the risk, I think we can do these choice again #156 (comment) . What do you think? |
I think you are right, since we are exposing So something like this would make it reliable for those who might want to call it to get access to the client being used for whatever reason. export const getActiveClient = () => (getCurrentInstance() && inject(VILLUS_CLIENT)) || activeClient; I don't think it should be unified with
Would say if you want to unify it, then the second behavior should be fine to include but not the first one nor the third. I'm thinking it should behave similar to Vue's I think this is the best value it can offer, since you don't really want it to throw unless it is critical, consider how people will use it in either case: const client = getActiveClient();
if (!client) {
// do something
} This is much nicer than: try {
const client = getActiveClient();
} catch (err) {
// do something
} Meanwhile it is perfectly fine for I suggest So it might look like this eventually: export const getActiveClient = () => {
const vm = getCurrentInstance();
if (!vm) {
return activeClient;
}
return inject(VILLUS_CLIENT, vm?.provides?.[VILLUS_CLIENT]) || activeClient;
} This should address some of your concerns, What do you think? |
@logaretm I don't think unified solution is a good idea either, it seems make something complicated, and thank you for making it clearly. export const getActiveClient = () => {
const vm = getCurrentInstance();
if (!vm) {
return activeClient;
}
return inject(VILLUS_CLIENT, vm?.provides?.[VILLUS_CLIENT]) || activeClient;
} I do think the suggested If we don't need the default value of export const getActiveClient = () => (getCurrentInstance() && inject(VILLUS_CLIENT)) || activeClient; |
Not really. Currently in Vue, the default In this example let's say we don't use the // setup
useClient();
getActiveClient();
useQuery(); both However this will cause an issue if both the parent and child components call So this part export const getActiveClient = () => {
const vm = getCurrentInstance();
if (!vm) {
return activeClient;
}
return vm.provides?.[VILLUS_CLIENT] || inject(VILLUS_CLIENT, activeClient);
} |
@logaretm Got it, thanks for your patient to explain clearly. Your suggest is more reliable and make sense. I've add the code to this PR. You can have a check. It seems that the things I can do is all done. If there is any help I can do, please let me know. |
Awesome @GavinXue, no worries at all and thank you a lot for this awesome PR. I will merge it now and I will be releasing it within a couple of days to make sure I add documentation for it 👍 |
I'd love to use useQuery, useMutation, but we only can define it in setup. so I extended it to use outside of setup and component. The idea to extends comes from pinia.
I create a sampe here: https://stackblitz.com/edit/vue-9ebjy6?file=src/App.vue , and this commit should enable it.
something should be notice:
manualClient
) in useQuery etc, to enable these function outside of component. eg: before app mounted. We can manually send the created client to enable it.This commit should fix #149;
hope it will be help.