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

[Qwik] Type checking for forms with variants #242

Open
ianlet opened this issue Aug 23, 2024 · 1 comment
Open

[Qwik] Type checking for forms with variants #242

ianlet opened this issue Aug 23, 2024 · 1 comment
Assignees
Labels
question Further information is requested

Comments

@ianlet
Copy link

ianlet commented Aug 23, 2024

Let's say I have the following schema with multiple variants requiring different fields based on the placeType field.

Here's the simplified version so I can better refer to it below:

const BaseSchema = v.object({
  name: v.pipe(v.string(), v.nonEmpty()),
  coordinates: v.object({
    latitude: v.number(),
    longitude: v.number(),
  }),
  thumbnailUrl: v.optional(v.pipe(v.string(), v.url())),
  websiteUrl: v.optional(v.pipe(v.string(), v.url())),
});

const FeaturesSchema = v.object({
  features: v.pipe(v.array(v.picklist(PlaceFeatures)), v.minLength(1)),
});

const DescriptionSchema = v.object({
  description: v.optional(v.string()),
});

const AddActivitySchema = v.object({
  ...BaseSchema.entries,
  ...FeaturesSchema.entries,
});

const AddDestinationSchema = v.object({
  ...BaseSchema.entries,
  ...FeaturesSchema.entries,
  ...DescriptionSchema.entries,
});


export const AddPlaceSchema = v.variant("placeType", [
  v.object({
    placeType: v.literal("activity"),
    ...AddActivitySchema.entries,
  }),
  v.object({
    placeType: v.literal("destination"),
    ...AddDestinationSchema.entries,
  }),
  v.object({
    placeType: v.picklist(PlaceTypes),
    ...BaseSchema.entries,
  }),
]);

export type AddPlaceForm = v.InferInput<typeof AddPlaceSchema>;

I want to use this schema to build a form in Qwik, but I'm having difficulties taming the Typescript compiler because it's always inferring that features is an undefined field or never.

Here's a minimal example to illustrate this issue:

const AddPlaceForm = component$(
  () => {
    const [addPlaceForm, { Form, Field }] = useForm<AddPlaceForm>({
      loader: {
        value: {
          name: "",
          thumbnailUrl: undefined,
          websiteUrl: undefined,
          coordinates: placeCoordinates,
          placeType: undefined,
          features: [],
          description: undefined,
        },
      },
      action: useAddPlaceAction(),
      validate: valiForm$(AddPlaceSchema),
    });

    const placeType = useComputed$(() => getValue(addPlaceForm, "placeType"));

    return (
      <Form>
           {placeType.value === "activity" && (
             <>
               {placeFeatures.value.map((feature) => (
                 <Field
                   name="features"
                   type="string[]"
                   key={`feature-${feature}`}
                 >
                   {(field, props) => (
                     <Checkbox
                       {...props}
                       value={feature}
                       checked={field.value?.includes(feature)}
                     >
                       {feature}
                     </Checkbox>
                   )}
                 </Field>
               ))}
             </>
           )}
      </Form>
    );
});

Which will yield the following errors:

src/components/map/places/add-place-modal.tsx:218:23 - error TS2322: Type 'string' is not assignable to type 'undefined'.

218                       type="string[]"
                          ~~~~

  ../../node_modules/@modular-forms/qwik/dist/types/components/Field.d.ts:20:5
    20     type: FieldType<FieldPathValue<TFieldValues, TFieldName>>;
           ~~~~
    The expected type comes from property 'type' which is declared here on type 'IntrinsicAttributes & Pick<Partial<Omit<FieldProps<{ name: string; coordinates: { latitude: number; longitude: number; }; features: ("workspace" | "skiing" | "snowshoeing" | "nordic-skiing" | ... 8 more ... | "bathroom")[]; placeType: "activity"; link?: string | undefined; th...'

src/components/map/places/add-place-modal.tsx:225:49 - error TS2339: Property 'includes' does not exist on type 'never'.

225                           checked={field.value?.includes(feature)}

Do you have an (elegant) recommendation on how to satisfy the compiler and let it know that we want a specific variant of the form in that case?

@fabian-hiller
Copy link
Owner

Yes, this is a problem with the current implementation of Modular Forms, which I will fix when I rewrite the library. To satisfy the compiler, you could add features: v.undefined() wherever features is not needed. The initial object structure for .loader is needed to initialize the form store.

@fabian-hiller fabian-hiller self-assigned this Aug 25, 2024
@fabian-hiller fabian-hiller added the question Further information is requested label Aug 25, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

2 participants