form-atoms / form-atoms

Atomic form primitives for Jotai

Home Page:https://codesandbox.io/s/getting-started-with-form-atoms-v2-ddhgq2?file=/src/App.tsx

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

`FormAtomValues` hints wrong type to the developer

MiroslavPetrik opened this issue Β· comments

Describe the bug
The return type of FormAtomValues should contain only primitive language types, while it still mentions FieldAtom generics.
The type itself is correct, but the developer experience could be better.

Case 1: array of field atoms FieldAtom<T>[]

const imageFields = {
  images: [
    fieldAtom({
      value: { id: "1", url: "https://" },
    }),
    fieldAtom({
      value: { id: "2", url: "https://" },
    }),
  ],
};

// when hovered/inspected
type ImageValuesACTUAL = FormAtomValues<typeof imageFields>;

// ISSUE1: the [x: number] is ugly way to say there is array of items
// ISSUE2: FieldAtom is obfuscating the actual type
// ISSUE3: there is recursive call to FormAtomValues
type ImageValuesACTUAL = {
  images: FormAtomValues<{
    [x: number]: FieldAtom<{
      id: string;
      url: string;
    }>;
  }>;
};

// FIX1: the array has intuitive syntax []
// FIX2: we have plain values, not boxes of FieldAtom
// FIX3: no recursive call
type ImageValuesEXPECTED = {
  images: {
    id: string;
    url: string;
  }[];
};

// Fix of https://github.com/jaredLunde/form-atoms/blob/main/src/index.tsx#L1208
type _FormAtomValues<Fields extends FormAtomFields> = {
  [Key in keyof Fields]: Fields[Key] extends FieldAtom<infer Value>
    ? Value
    : Fields[Key] extends FormAtomFields
    ? _FormAtomValues<Fields[Key]>
    : Fields[Key] extends FieldAtom<infer Value>[] // FIX2: here go deeper into array of FieldAtom; no need to recurr
    ? Value[]
    : Fields[Key] extends FormAtomFields[] // FIX1: fixes the [x: number] for Case 2 below
    ? _FormAtomValues<FormAtomFields>
    : never;
};

Case 2: array of 'fields' {[string]: FieldAtom<any>}[]

const addressesFields = {
  addresses: [
    {
      city: fieldAtom({ value: "Stockholm" }),
      street: fieldAtom({ value: "Carl Gustav Street" }),
    },
    {
      city: fieldAtom({ value: "Bratislava" }),
      street: fieldAtom({ value: "Kosicka" }),
    },
  ],
};

type AddressValuesACTUAL = FormAtomValues<typeof addressesFields>;

// when hovered 
type AddressValuesACTUAL = {
    addresses: FormAtomValues<{
        [x: number]: {
            city: FieldAtom<string>;
            street: FieldAtom<string>;
        };
    }>;
}

type AddressValuesEXPECTED = {
    addresses: {
        city: string;
        street: string;
    }[];
}

// Now the fix is a bit trickier - the arrays does not contain leaf nodes, they have objects, so we must recurr,
// but recurr would keep the `FormAtomValues` call indication in the result, and obfuscate it
// SO WE CAN recurr by duplicating the code.
// This does not cover all cases, e.g. deeply nested form definitions would be still 'bad', but certain level, e.g. depth 2
// could be supported to cover most user needs:
type _FormAtomValues<Fields extends FormAtomFields> = {
  [Key in keyof Fields]: Fields[Key] extends FieldAtom<infer Value>
    ? Value
    : Fields[Key] extends FormAtomFields
    ? _FormAtomValues<Fields[Key]>
    : Fields[Key] extends FieldAtom<infer Value>[]
    ? Value[]
    : Fields[Key] extends (infer NestedFields)[]
    ? NestedFields extends FormAtomFields
      ? {
          [K2 in keyof NestedFields]: NestedFields[K2] extends FieldAtom<
            infer Value
          >
            ? Value
            : NestedFields[K2] extends FormAtomFields
            ? _FormAtomValues<NestedFields[K2]>
            : NestedFields[K2] extends any[]
            ? _FormAtomValues<NestedFields[K2][number]>
            : never;
        }[]
      : never
    : never;
};

Additional context

I prefer the current type as it is more readable and thus easier to maintain.

Am I missing something? Wouldn't this work nearly as well while remaining readable? I get that it doesn't display to the developer perfectly, but it is a slight improvement that type checks correctly which is the important thing.

export type FormFieldValues<Fields extends FormFields> = {
  [Key in keyof Fields]: Fields[Key] extends FieldAtom<infer Value>
    ? Value
    : Fields[Key] extends FormFields
    ? FormFieldValues<Fields[Key]>
    : Fields[Key] extends Array<infer Item>
    ? Item extends FieldAtom<infer Value>
      ? Value[]
      : Item extends FormFields
      ? FormFieldValues<Item>[]
      : never
    : never;
};

Screen Shot 2023-02-01 at 5 13 42 PM

Screen Shot 2023-02-01 at 5 13 27 PM

πŸŽ‰ This issue has been resolved in version 2.0.0-next.1 πŸŽ‰

The release is available on:

Your semantic-release bot πŸ“¦πŸš€

@jaredLunde Yes, the version from case 1 will be ok, as it is an improvement.

I don't like the readability of the _FormAtomValues from case 2 neither, it was only illustration that its possible to achieve :)

Maybe typescript will improve this automatically in the future, by eagerly resolving the 'generit type calls' instead of displaying them. Who knows.

πŸŽ‰ This issue has been resolved in version 2.0.0 πŸŽ‰

The release is available on:

Your semantic-release bot πŸ“¦πŸš€