vantezzen / auto-form

🌟 A React component that automatically creates a @shadcn/ui form based on a zod schema.
https://vantezzen.github.io/auto-form/
2.27k stars 81 forks source link

Skip ZodArray default behaviour for custom component #78

Open JumboWumbus opened 1 month ago

JumboWumbus commented 1 month ago

Hello, I have an issue currently. I have tried my best to follow the information provided in creating and using custom input components; I've successfully created an auto-form combobox from the information provided.

But now I'm trying to make a multi file uploader, which uses z.array(z.instanceof(File)). The fact that the zodItem is of type ZodArray is causing it to be rendered as an accordian before it can be rendered as my custom component.

My current workaround for the issue is checking if the name of the item is also not images.

I'd like to check for type File or something more robust than the current solution but I am a silly billy with a small brain.

Here's my current workaround:

object.tsx the default auto-form component with my changes to the IF statement

...

export default function AutoFormObject ...
  return (
    <Accordion type="multiple" className="space-y-5 border-none">
      {Object.keys(shape).map((name) => {

...      

        if (zodBaseType === 'ZodArray' && name !== 'images') {
          return (
            <AutoFormArray
              key={key}
              name={name}
              item={item as unknown as z.ZodArray<any>}
              form={form}
              fieldConfig={fieldConfig?.[name] ?? {}}
              path={[...path, name]}
            />
          );
        }

Here is the rest of my relevant code:

config.ts

import { FormFileUploader } from "@/app/_components/ui/auto-form/fields/fileUploader";
import AutoFormCheckbox from "./fields/checkbox";
import AutoFormDate from "./fields/date";
import AutoFormEnum from "./fields/enum";
import AutoFormFile from "./fields/file";
import AutoFormInput from "./fields/input";
import AutoFormNumber from "./fields/number";
import AutoFormRadioGroup from "./fields/radio-group";
import AutoFormSwitch from "./fields/switch";
import AutoFormTextarea from "./fields/textarea";
import { FormCombobox } from "@/app/_components/ui/auto-form/fields/combobox";

export const INPUT_COMPONENTS = {
  combobox: FormCombobox,
  fileUploader: FormFileUploader,
  checkbox: AutoFormCheckbox,
  date: AutoFormDate,
  select: AutoFormEnum,
  radio: AutoFormRadioGroup,
  switch: AutoFormSwitch,
  textarea: AutoFormTextarea,
  number: AutoFormNumber,
  file: AutoFormFile,
  fallback: AutoFormInput,
};

/**
 * Define handlers for specific Zod types.
 * You can expand this object to support more types.
 */
export const DEFAULT_ZOD_HANDLERS: {
  [key: string]: keyof typeof INPUT_COMPONENTS;
} = {
  ZodBoolean: "checkbox",
  ZodDate: "date",
  ZodEnum: "select",
  ZodNativeEnum: "select",
  ZodNumber: "number",
};

fileUploader.tsx my custom component that should be used instead of AutoFormArray

'use client';

import { toast } from 'sonner';
import {
  Form,
  FormControl,
  FormDescription,
  FormField,
  FormItem,
  FormLabel,
  FormMessage,
} from '@/app/_components/ui/atoms/form';

import { AutoFormInputComponentProps } from '@/app/_components/ui/auto-form/types';
import * as z from 'zod';
import { getBaseSchema } from '@/app/_components/ui/auto-form/utils';
import { useState } from 'react';
import AutoFormLabel from '@/app/_components/ui/auto-form/common/label';
import { FileUploader } from '@/app/_components/ui/organisms/fileUploader/fileUploader';

interface FormFileUploaderProps
  extends Pick<
    AutoFormInputComponentProps,
    | 'label'
    | 'field'
    | 'fieldConfigItem'
    | 'fieldProps'
    //  | 'zodItem'
    | 'isRequired'
  > {
  // add any additional properties here
}

export function FormFileUploader({
  label,
  isRequired,
  field,
  fieldConfigItem,
  // zodItem,
  fieldProps,
}: FormFileUploaderProps) {
  const [loading, setLoading] = useState(false);

  //const baseValues = zodItem;

  return (
    <FormItem>
      <AutoFormLabel
        label={fieldConfigItem?.label || label}
        isRequired={isRequired}
      />
      <FormControl>
        <FileUploader
          value={field.value}
          onValueChange={field.onChange}
          maxFiles={15}
          maxSize={4 * 1024 * 1024}
        />
      </FormControl>
      <FormMessage />
    </FormItem>
  );
}

page.tsx my schema and configuration for auto-form

The odd structure is because I let the user select between different forms

  const StaySchema = z.object({
    //age: z.number(),
    //fullAddress: z.string(),
    streetName: z.string(),
    city: z.string(),
    postalCode: z.string(),
    country: z.nativeEnum(countryEnum).describe('Country'),
    sendMeMails: z.boolean(),
    images: z.array(z.instanceof(File)),
  });

  const StaySchemaFieldConfig: FieldConfig<
    z.infer<typeof StaySchema>
  > = {
    streetName: {
      inputProps: {
        name: 'streetName',
      },
    },
    city: {
      inputProps: {
        name: 'city',
      },
    },
    postalCode: {
      inputProps: {
        name: 'postalCode',
      },
    },
    country: {
      inputProps: {
        name: 'country',
        placeholder: 'Select a country...',
      },
      fieldType: 'combobox',
    },

    images: {
      inputProps: {
        name: 'images',
      },
      label: 'Upload images',
      fieldType: 'fileUploader',
    },
  };

Any help or suggestions would be appreciated.