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

Vue query improve reactive support #852

Merged
merged 20 commits into from
May 19, 2023
Merged

Conversation

gkweb
Copy link
Contributor

@gkweb gkweb commented May 6, 2023

Status

READY/WIP/HOLD

READY

Description

Addresses: #851

A few sentences describing the overall goals of the pull request's commits.

Improves vue reactivity support.

Related PRs

List related PRs against other branches:

branch PR
other_pr_production link
other_pr_master link

Todos

  • Tests
  • Documentation
  • Changelog Entry (unreleased)

Steps to Test or Reproduce

Outline the steps to test or reproduce the PR here.

See the sample update, I've expanded on the sample by dynamically updating the list to load in a single pet.

> git pull --prune
> git checkout <branch>
> grunt jasmine

@vercel
Copy link

vercel bot commented May 6, 2023

Someone is attempting to deploy a commit to a Personal Account owned by @anymaniax on Vercel.

@anymaniax first needs to authorize it.

@gkweb
Copy link
Contributor Author

gkweb commented May 7, 2023

@anymaniax - Now ready for review, remaining type fixes added. Thank you for your consideration.

@Maxim-Mazurok
Copy link
Contributor

This seems to work quite well for me

@anymaniax
Copy link
Collaborator

I see that the tests are not passing. There is a problem for react-query and svelte-query

@gkweb
Copy link
Contributor Author

gkweb commented May 8, 2023

I see that the tests are not passing. There is a problem for react-query and svelte-query

@anymaniax - Are you available on a chat anywhere? Or can you share the output of failure here? I can take a look then! Many Thanks

@anymaniax
Copy link
Collaborator

I try to be available on the Orval discord if you need to talk to me. You will see the problem if in the project you run the tests in the tests folder you just need to install the dependencies in it and then yarn generate && yarn build

props.filter((prop) => prop.type !== GetterPropType.HEADER),
'implementation',
);

if (OutputClient.VUE_QUERY) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@anymaniax isOutputClient.VUE_QUERY a reliable way to detect if the runner is vue only?

Ta

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes it's the only way to know it

@gkweb
Copy link
Contributor Author

gkweb commented May 8, 2023

I see that the tests are not passing. There is a problem for react-query and svelte-query

@anymaniax - Are you available on a chat anywhere? Or can you share the output of failure here? I can take a look then! Many Thanks

All good I see this now!

Left a comment above. Will join discord. Ta

@Maxim-Mazurok
Copy link
Contributor

Maxim-Mazurok commented May 9, 2023

As I've demonstrated in #851 (comment) the query isn't being refetched when selected petId changes.
This seems to fix the issue but I hope there's a better way:

import { watch } from 'vue';
watch(petId, () => {
  petQuery.refetch();
});

@Maxim-Mazurok
Copy link
Contributor

Maxim-Mazurok commented May 9, 2023

I think I figured it out.
In getShowPetByIdQueryKey you're doing unref() so that you can return a string.
If we instead return an array (which is also a good key option for Vue Query as far as I understand) - then array values will still be reactive.
So this works without manual watcher:

export const getShowPetByIdQueryKey = (petId: MaybeRef<string>, version = 1) =>
-  [`/v${unref(version)}/pets/${unref(petId)}`] as const;
+  [`/v`, version, `/pets/`, petId] as const;

@gkweb
Copy link
Contributor Author

gkweb commented May 9, 2023

I think I figured it out.

In getShowPetByIdQueryKey you're doing unref() so that you can return a string.

If we instead return an array (which is also a good key option for Vue Query as far as I understand) - then array values will still be reactive.

So this works without manual watcher:

export const getShowPetByIdQueryKey = (petId: MaybeRef<string>, version = 1) =>

-  [`/v${unref(version)}/pets/${unref(petId)}`] as const;

+  [`/v`, version, `/pets/`, petId] as const;

Thanks @Maxim-Mazurok

I agree with the query key solution as Vue-query handles it and unwraps automatically. Let me take a look when I get some time. My first suggestion was to update queryKey also. Cheers

@Maxim-Mazurok
Copy link
Contributor

Maxim-Mazurok commented May 11, 2023

Found another small issue:
image

This seems to fix that:

diff --git a/packages/query/src/index.ts b/packages/query/src/index.ts
index 4f62c9a..e126713 100644
--- a/packages/query/src/index.ts
+++ b/packages/query/src/index.ts
@@ -402,10 +402,11 @@ const generateQueryRequestFunction = (
     hasSignal,
   });
 
-  return `export const ${operationName} = (\n    ${toObjectString(
-    props,
-    'implementation',
-  )} ${optionsArgs} ): Promise<AxiosResponse<${
+  const queryProps = isVue(outputClient)
+    ? vueWrapTypeWithMaybeRef(toObjectString(props, 'implementation'))
+    : toObjectString(props, 'implementation');
+
+  return `export const ${operationName} = (\n    ${queryProps} ${optionsArgs} ): Promise<AxiosResponse<${
     response.definition.success || 'unknown'
   }>> => {${bodyForm}
     return axios${

@Maxim-Mazurok
Copy link
Contributor

Found another option which also works:

export const getShowPetByIdQueryKey = (petId: MaybeRef<string>, version = 1) =>
  computed(() => [`/v${unref(version)}/pets/${unref(petId)}`]);

@Maxim-Mazurok
Copy link
Contributor

Maxim-Mazurok commented May 11, 2023

Implemented following option based on this PR

[`/v`, version, `/pets`]

gkweb#1

@gkweb
Copy link
Contributor Author

gkweb commented May 12, 2023

@anymaniax tests are now passing for this, so awaiting your review.

@Maxim-Mazurok
Copy link
Contributor

[/v, version, /pets]

I did it this way because I wasn't sure how Vue Query hashes the key, I thought that if it does [].join("") for example - then /v1/air/plane would have the same key as /v1/airplane which would be a mistake.

Checking out their code, I've created a test to confirm that we can drop / from the keys and not get collisions. Keys are hashed using JSON.stringify() so commas will serve as delimiters, no need for / indeed. Here's the test:

function isPlainObject(o) {
  if (!hasObjectPrototype(o)) {
    return false
  }

  // If has modified constructor
  const ctor = o.constructor
  if (typeof ctor === 'undefined') {
    return true
  }

  // If has modified prototype
  const prot = ctor.prototype
  if (!hasObjectPrototype(prot)) {
    return false
  }

  // If constructor does not have an Object-specific method
  if (!prot.hasOwnProperty('isPrototypeOf')) {
    return false
  }

  // Most likely a plain Object
  return true
}

function hasObjectPrototype(o) {
  return Object.prototype.toString.call(o) === '[object Object]'
}

function hashQueryKey(queryKey) {
  return JSON.stringify(queryKey, (_, val) =>
    isPlainObject(val)
      ? Object.keys(val)
          .sort()
          .reduce((result, key) => {
            result[key] = val[key]
            return result
          }, {})
      : val,
  )
}

console.log(hashQueryKey(["v", 1, "air", "plane"])) // prints ["v",1,"air","plane"]
console.log(hashQueryKey(["v", 1, "airplane"])) // prints ["v",1,"airplane"]

@Maxim-Mazurok
Copy link
Contributor

Maxim-Mazurok commented May 15, 2023

Just tested this on my project and it generates everything well and works well, so LGTM.

There's another reactivity issue tho, now with enabled option.

Basically, we have this now: enabled: !!myArgument and since myArgument is a ref/proxy - enabled is always true.

I've solved it by doing this:

- return { queryKey, queryFn, enabled: !!myArgument, ...queryOptions };
+ return { queryKey, queryFn, enabled: computed(() => !!unref(myArgument)), ...queryOptions };

Seems to work fine. I'll submit PR

@@ -49,7 +49,7 @@ export const listPets = (
export const getListPetsQueryKey = (
params?: MaybeRef<ListPetsParams>,
version = 1,
) => [`/v`, version, `/pets`, ...(params ? [params] : [])] as const;
) => ['v', version, 'pets', ...(params ? [params] : [])] as const;
Copy link
Contributor Author

@gkweb gkweb May 15, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Maxim-Mazurok

RE: I did it this way because I wasn't sure how Vue Query hashes the key, I thought that if it does [].join("") for example - then /v1/air/plane would have the same key as /v1/airplane which would be a mistake.

I've changed what functionality you had here previously. Is this what you're describing as a requirement? (forward slash removed). Any string concatenation with variables even in the pathname should be cut up and recognised as a var.
eg: /something${var}/pets/${petId} will explode to ['something',var,'pets',petId]

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's all good now, thank you. I was just confirming that we can get rid of slashes and keep arrays just like you did, all good currently 👍

@gkweb
Copy link
Contributor Author

gkweb commented May 15, 2023

Just tested this on my project and it generates everything well and works well, so LGTM.

There's another reactivity issue tho, now with enabled option.

Basically, we have this now: enabled: !!myArgument and since myArgument is a ref/proxy - enabled is always true.

I've solved it by doing this:

- return { queryKey, queryFn, enabled: !!myArgument, ...queryOptions };
+ return { queryKey, queryFn, enabled: computed(() => !!unref(myArgument)), ...queryOptions };

Seems to work fine. I'll submit PR

vue-query supports ref here I'm pretty sure, So this only needs to be changed to: enabled: myArgument right? - If you need to expand on that just pass in your own enabled implementation?

@@ -849,7 +878,7 @@ export const ${camel(
`${operationPrefix}-${type}`,
)}(${queryOptionsVarName}) as ${returnType};

query.queryKey = ${queryOptionsVarName}.queryKey;
query.queryKey = ${queryOptionsVarName}.queryKey as QueryKey;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to apply to samples/react-query/hook-mutator/endpoints.ts as well as vue one, not sure if that was your intention.

@Maxim-Mazurok
Copy link
Contributor

vue-query supports ref here I'm pretty sure

It doesn't seem to be the case...

So this only needs to be changed to: enabled: myArgument right?

Nope, that doesn't work, it's being evaluated to true when I pass ref(undefined).

If you need to expand on that just pass in your own enabled implementation?

I guess I could... But I rather patch it up in the generator, see gkweb#2 We don't have to include it in this PR tho if you'd like to do it separately. Seems like a small fix to me tho.

@anymaniax
Copy link
Collaborator

Hello guys, thanks for the work can I do something here? is the pull request ready?

@gkweb
Copy link
Contributor Author

gkweb commented May 17, 2023

Hello guys, thanks for the work can I do something here? is the pull request ready?

Hey @anymaniax - yeah any subsequent issue can be fixed in additional pull requests.
There are some more improvements which can still be done here but this has paved the way to improving reactivity.

@anymaniax
Copy link
Collaborator

Just did a first tour. Insane work thanks guys ❤️

import { useShowPetById } from '../api/endpoints/petstoreFromFileSpecWithTransformer';

const props = defineProps<{ petId: string }>();
const petId = computed(() => props.petId);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the computed is not really need here right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The props is a reactive object. Wrapping in the computed can help to demonstrate reactivity of the individual prop though?

https://vuejs.org/guide/extras/reactivity-transform.html#reactive-props-destructure

Let me know if you want this changed?

Copy link
Collaborator

@anymaniax anymaniax May 19, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no it was to be sure

Copy link
Collaborator

@anymaniax anymaniax left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks 🎉

@anymaniax anymaniax merged commit 64070a6 into orval-labs:master May 19, 2023
@AlmarAubel
Copy link
Contributor

Great work, any idea when you will release this to NPM?

@anymaniax
Copy link
Collaborator

anymaniax commented May 23, 2023

This week. I want to add one more pull request before the release.

@anymaniax
Copy link
Collaborator

Changed my mind it's released right now

Maxim-Mazurok referenced this pull request Oct 9, 2023
* fix: correct `jsStringEscape` import

* feat(namedParameters): add global `useNamedParameters` option

* feat(namedParameters): add NAMED_PATH_PARAMS prop type

* test(axios): add named parameters test

* test(angular): add named parameters test

* test(react-query): add named parameters test

* fix(query): introduce destructured usage

* test(svelte-query): add named parameters test

* test(vue-query): add named parameters test

* fix(vue-query): rework MaybeRef logic

* fix(swr): introduce destructured usage

* test(swr): add named parameters test

* chore(cra): update react-scripts

* fix(angular-app): update sample

* fix(react-app): update sample

* fix(react-app-with-swr): update sample

* fix(react-query/basic-app): update sample

* fix(react-query/custom-client-app): update sample

* fix(react-query/form-data-app): update sample

* fix(react-query/form-data-mutator-app): update sample

* fix(react-query/form-url-encoded-mutator-app): update sample

* fix(react-query/hook-mutator-app): update sample

* fix(svelte-query): update sample

* fix(vue-query-app): update sample

* fix(namedParameters): optional arg when all params are optional
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants