Skip to content

useAsyncData handles discriminated unions incorrectly #14698

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

Closed
roamiiing opened this issue Aug 24, 2022 · 3 comments · Fixed by nuxt/framework#9061
Closed

useAsyncData handles discriminated unions incorrectly #14698

roamiiing opened this issue Aug 24, 2022 · 3 comments · Fixed by nuxt/framework#9061

Comments

@roamiiing
Copy link

Environment

Reproduction

declare const discriminated:
  | { status: 'resolved'; result: string }
  | { status: 'rejected'; error: string }

const { data } = useAsyncData('discriminated', async () => discriminated)

if (data.value.status === 'resolved') {
  // Property 'result' does not exist on type 'Pick<{ status: "resolved"; result: string; }, "status">'.ts(2339)
  data.value.result
} else {
  // Property 'error' does not exist on type 'Pick<{ status: "rejected"; error: string; }, "status">'.ts(2339)
  data.value.error
}

Describe the bug

Hello! I'm trying to use discriminated unions with useAsyncData and getting an incorrect type of the data ref return from useAsyncData as stated in the reproduction. The resulting type of data is:

const data: Ref<Pick<{
    status: 'resolved';
    result: string;
}, "status"> | Pick<{
    status: 'rejected';
    error: string;
}, "status">>

The only field that is picked from a union is status which exists in both union cases. I think that issue starts here: https://github.com/nuxt/framework/blob/1700bf822e31ffe2c9ec66b61d6f6562d45dcf2b/packages/nuxt/src/app/composables/asyncData.ts#L15

The keyof operator in Typescript returns only common keys for discriminated unions. I suggest to give a default type of undefined or null to the PickKeys type parameter of the useAsyncData function. That way we could check if picking is needed in the result type like this:

export declare function useAsyncData<
  DataT,
  DataE = Error,
  Transform extends _Transform<DataT> = _Transform<DataT, DataT>,
  PickKeys extends KeyOfRes<Transform> = null,
>(
  key: string,
  handler: (ctx?: NuxtApp) => Promise<DataT>,
  options?: AsyncDataOptions<DataT, Transform, PickKeys>,
): AsyncData<
  PickKeys extends KeyOfRes<Transform>
    ? PickFrom<ReturnType<Transform>, PickKeys>
    : DataT,
  DataE | null | true
>

And the typings for discriminated unions become correct:

declare const discriminated:
  | { status: 'resolved'; result: string }
  | { status: 'rejected'; error: string }

const { data } = useAsyncData('discriminated', async () => discriminated)

if (data.value.status === 'resolved') {
  // Ref<{ resolved: true; result: string; } | { resolved: false; error: string; }>.value: {
  //     resolved: true;
  //     result: string;
  // }
  data.value.result
} else {
  // Ref<{ status: 'resolved'; result: string; } | { status: 'rejected'; error: string; }>.value: {
  //     status: 'rejected';
  //     error: string;
  // }
  data.value.error
}

declare const nonDiscriminated: { a: number; b: string; c: boolean }

const { data: data2 } = useAsyncData(
  'discriminated',
  async () => nonDiscriminated,
  {
    pick: ['a'],
  },
)

// Ref<Pick<{ a: number; b: string; c: boolean; }, "a">>.value: Pick<{
//     a: number;
//     b: string;
//     c: boolean;
// }, "a">
data2.value

I could submit a PR if this approach is acceptable.

Additional context

No response

Logs

No response

@danielroe
Copy link
Member

@roamiiing Thank you for this! Please, do feel free to submit a PR. I'd suggest adding a few extra type tests in test/fixtures/basic/types.ts to ensure we don't break any current types.

@roamiiing
Copy link
Author

roamiiing commented Aug 25, 2022

Well actually there're more issues related to this. You also can't pick keys that were introduced in transform function because of some weird behaviour of TS type params.

Screenshot 2022-08-25 at 16 03 53

I'm currently investigating how to fix all of that with minimal changes.

@tobiasdiez
Copy link
Contributor

Not exactly the same issue, but related:

const { data } = await useFetch<string>(
  '<some url returning a simple string>',
  {
    transform: (data) => data.split('\n'),
  }
)

Typescript complains about the return type of transform.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants