Coder Social home page Coder Social logo

vantezzen / auto-form Goto Github PK

View Code? Open in Web Editor NEW
1.9K 15.0 67.0 424 KB

🌟 A React component that automatically creates a @shadcn/ui form based on a zod schema.

Home Page: https://vantezzen.github.io/auto-form/

JavaScript 4.25% HTML 0.63% TypeScript 93.54% CSS 1.58%
form-validation react react-component shadcn-ui

auto-form's Introduction

<AutoForm /> for @shadcn/ui

AutoForm is a React component that automatically creates a @shadcn/ui form based on a zod schema.

A live demo can be found at https://vantezzen.github.io/auto-form/.

AutoForm demo

When to use AutoForm?

AutoForm is mostly meant as a drop-in form builder for your internal and low-priority forms with existing zod schemas. For example, if you already have zod schemas for your API and want to create a simple admin panel to edit user profiles, simply pass the schema to AutoForm and you're done.

AutoForm uses @shadcn/ui components as natively as possible with only minimal class overrides. This way, if you have customized your @shadcn/ui components in your project, AutoForm should not interfere with your customizations.

As forms almost always grow more complex, AutoForm gives you options to customize how forms are rendered (e.g. using the fieldConfig options and dependency support) and gives you escape hatches to customize the form even further (e.g. rendering custom parents and adding custom field types).

However, AutoForm does not aim to be a full-featured form builder. It does not aim to support every edge case in your zod schema or allow building complex, multi-page forms. If you need more customization, feel free to customize AutoForm's renderer in your project or use more powerful form builders like Formik - though those require more specialized configuration instead of simple drop-in support for your zod schema. For an example on how AutoForm can be extended for more powerful, YAML-based, multi-page forms, see AutoForm YAML.

Installation

The component depends on the following components from shadcn/ui:

  • accordion
  • button
  • calendar
  • card
  • checkbox
  • form
  • input
  • label
  • popover
  • radio-group
  • select
  • separator
  • switch
  • textarea
  • tooltip
  • toggle

You can install them all at once with:

npx shadcn-ui@latest add accordion button calendar card checkbox form input label popover radio-group select separator switch textarea tooltip toggle

To install the component itself, copy the auto-form folder and date-picker.tsx from src/components/ui to your project's ui folder.

You can remove the tests folder in auto-form/tests.

Field types

Currently, these field types are supported out of the box:

  • boolean (checkbox, switch)
  • date (date picker)
  • enum (select, radio group)
  • number (input)
  • string (input, textfield)

You can add support for other field types by adding them to the INPUT_COMPONENTS object in auto-form/config.tsx.

Usage

Basic usage:

"use client";
import AutoForm, { AutoFormSubmit } from "./components/ui/auto-form";
import * as z from "zod";

// Define your form schema using zod
const formSchema = z.object({
  username: z
    .string({
      required_error: "Username is required.",
    })
    // You can use zod's built-in validation as normal
    .min(2, {
      message: "Username must be at least 2 characters.",
    }),

  password: z
    .string({
      required_error: "Password is required.",
    })
    // Use the "describe" method to set the label
    // If no label is set, the field name will be used
    // and un-camel-cased
    .describe("Your secure password")
    .min(8, {
      message: "Password must be at least 8 characters.",
    }),

  favouriteNumber: z.coerce // When using numbers and dates, you must use coerce
    .number({
      invalid_type_error: "Favourite number must be a number.",
    })
    .min(1, {
      message: "Favourite number must be at least 1.",
    })
    .max(10, {
      message: "Favourite number must be at most 10.",
    })
    .default(5) // You can set a default value
    .optional(),

  acceptTerms: z
    .boolean()
    .describe("Accept terms and conditions.")
    .refine((value) => value, {
      message: "You must accept the terms and conditions.",
      path: ["acceptTerms"],
    }),

  // Date will show a date picker
  birthday: z.coerce.date().optional(),

  sendMeMails: z.boolean().optional(),

  // Enum will show a select
  color: z.enum(["red", "green", "blue"]),

  // Create sub-objects to create accordion sections
  address: z.object({
    street: z.string(),
    city: z.string(),
    zip: z.string(),
  }),
});

function App() {
  return (
    <AutoForm
      // Pass the schema to the form
      formSchema={formSchema}
      // You can add additional config for each field
      // to customize the UI
      fieldConfig={{
        password: {
          // Use "inputProps" to pass props to the input component
          // You can use any props that the component accepts
          inputProps: {
            type: "password",
            placeholder: "••••••••",
          },
        },
        favouriteNumber: {
          // Set a "description" that will be shown below the field
          description: "Your favourite number between 1 and 10.",
        },
        acceptTerms: {
          inputProps: {
            required: true,
          },
          // You can use JSX in the description
          description: (
            <>
              I agree to the{" "}
              <a
                href="#"
                className="text-primary underline"
                onClick={(e) => {
                  e.preventDefault();
                  alert("Terms and conditions clicked.");
                }}
              >
                terms and conditions
              </a>
              .
            </>
          ),
        },

        birthday: {
          description: "We need your birthday to send you a gift.",
        },

        sendMeMails: {
          // Booleans use a checkbox by default, you can use a switch instead
          fieldType: "switch",
        },
      }}
      // Optionally, define dependencies between fields
      dependencies={[
        {
          // Hide "color" when "sendMeMails" is not checked as we only need to
          // know the color when we send mails
          sourceField: "sendMeMails",
          type: DependencyType.HIDES,
          targetField: "color",
          when: (sendMeMails) => !sendMeMails,
        },
      ]}
    >
      {/* 
      Pass in a AutoFormSubmit or a button with type="submit".
      Alternatively, you can not pass a submit button
      to create auto-saving forms etc.
      */}
      <AutoFormSubmit>Send now</AutoFormSubmit>

      {/*
      All children passed to the form will be rendered below the form.
      */}
      <p className="text-gray-500 text-sm">
        By submitting this form, you agree to our{" "}
        <a href="#" className="text-primary underline">
          terms and conditions
        </a>
        .
      </p>
    </AutoForm>
  );
}

Next.js and RSC

AutoForm can only be used inside a client-side React component due to serialization of the zod schema and values to your event listeners. If you want to use it in a Next.js app, simply mark your component with "use client":

// MyPage.tsx
export default function MyPage() {
  return (
    <div>
      <MyForm />
    </div>
  );
}

// MyForm.tsx
"use client";
import AutoForm from "./components/ui/auto-form";
export default function MyForm() {
  return <AutoForm onSubmit={...} ... />;
}

Zod configuration

Validations

Your form schema can use any of zod's validation methods including refine.

Autoform is able to automatically transform some of zod's validation elements into HTML attributes. For example, if you use zod.string().min(8), the input will automatically have a minlength="8" attribute.

Validation methods that are not supported by HTML will automatically be checked when the form is submitted.

Descriptions

You can use the describe method to set a label and description for each field. If no label is set, the field name will be used and un-camel-cased.

const formSchema = z.object({
  username: z.string().describe("Your username"),
  someValue: z.string(), // Will be "Some Value"
});

Coercion

When using numbers and dates, you should use coerce. This is because input elements may return a string that should automatically be converted.

const formSchema = z.object({
  favouriteNumber: z.coerce.number(),
  birthday: z.coerce.date(),
});

Optional fields

By default, all fields are required. You can make a field optional by using the optional method.

const formSchema = z.object({
  username: z.string().optional(),
});

Default values

You can set a default value for a field using the default method.

const formSchema = z.object({
  favouriteNumber: z.number().default(5),
});

If you want to set default value of date, convert it to Date first using new Date(val).

Sub-objects

You can nest objects to create accordion sections.

const formSchema = z.object({
  address: z.object({
    street: z.string(),
    city: z.string(),
    zip: z.string(),

    // You can nest objects as deep as you want
    nested: z.object({
      foo: z.string(),
      bar: z.string(),

      nested: z.object({
        foo: z.string(),
        bar: z.string(),
      }),
    }),
  }),
});

Like with normal objects, you can use the describe method to set a label and description for the section:

const formSchema = z.object({
  address: z
    .object({
      street: z.string(),
      city: z.string(),
      zip: z.string(),
    })
    .describe("Your address"),
});

Select/Enums

AutoForm supports enum and nativeEnum to create select fields.

const formSchema = z.object({
  color: z.enum(["red", "green", "blue"]),
});

enum BreadTypes {
  // For native enums, you can alternatively define a backed enum to set a custom label
  White = "White bread",
  Brown = "Brown bread",
  Wholegrain = "Wholegrain bread",
  Other,
}
// Keep in mind that zod will validate and return the enum labels, not the enum values!
const formSchema = z.object({
  bread: z.nativeEnum(BreadTypes),
});

Arrays

AutoForm supports arrays of objects. Because inferring things like field labels from arrays of strings/numbers/etc. is difficult, only objects are supported.

const formSchema = z.object({
  guestListName: z.string(),
  invitedGuests: z
    .array(
      // Define the fields for each item
      z.object({
        name: z.string(),
        age: z.coerce.number(),
      })
    )
    // Optionally set a custom label - otherwise this will be inferred from the field name
    .describe("Guests invited to the party"),
});

Arrays are not supported as the root element of the form schema.

You also can set default value of an array using .default(), but please makesure the array element has same structure with the schema.

const formSchema = z.object({
  guestListName: z.string(),
  invitedGuests: z
    .array(
      // Define the fields for each item
      z.object({
        name: z.string(),
        age: z.coerce.number(),
      })
    )
    .describe("Guests invited to the party")
    .default([
      {
        name: "John",
        age: 24,
      },
      {
        name: "Jane",
        age: 20,
      },
    ]),
});

Field configuration

As zod doesn't allow adding other properties to the schema, you can use the fieldConfig prop to add additional configuration for the UI of each field.

<AutoForm
  fieldConfig={{
    // Add config for each field here - don't add the field name to keep all defaults
    username: {
      // Configuration here
    },
  }}
/>

Input props

You can use the inputProps property to pass props to the input component. You can use any props that the HTML component accepts.

<AutoForm
  fieldConfig={{
    username: {
      inputProps: {
        type: "text",
        placeholder: "Username",
      },
    },
  }}
/>

// This will be rendered as:
<input type="text" placeholder="Username" /* ... */ />

Disabling the label of an input can be done by using the showLabel property in inputProps.

<AutoForm
  fieldConfig={{
    username: {
      inputProps: {
        type: "text",
        placeholder: "Username",
        showLabel: false,
      },
    },
  }}
/>

Field type

By default, AutoForm will use the Zod type to determine which input component to use. You can override this by using the fieldType property.

<AutoForm
  fieldConfig={{
    sendMeMails: {
      // Booleans use a checkbox by default, use a switch instead
      fieldType: "switch",
    },
  }}
/>

The complete list of supported field types is typed. Current supported types are:

  • "checkbox" (default for booleans)
  • "switch"
  • "date" (default for dates)
  • "select" (default for enums)
  • "radio"
  • "textarea"
  • "fallback" (default for everything else, simple input field)

Alternatively, you can pass a React component to the fieldType property to use a custom component.

<AutoForm
  fieldConfig={{
    sendMeMails: {
      fieldType: ({
        label,
        isRequired,
        field,
        fieldConfigItem,
        fieldProps,
      }: AutoFormInputComponentProps) => (
        <FormItem className="flex flex-row items-start space-x-3 space-y-0 rounded-md border p-4">
          <FormControl>
            <Switch
              checked={field.value}
              onCheckedChange={field.onChange}
              {...fieldProps}
            />
          </FormControl>
          <div className="space-y-1 leading-none">
            <FormLabel>
              {label}
              {isRequired && <span className="text-destructive"> *</span>}
            </FormLabel>
            {fieldConfigItem.description && (
              <FormDescription>{fieldConfigItem.description}</FormDescription>
            )}
          </div>
        </FormItem>
      ),
    },
  }}
/>

Description

You can use the description property to add a description below the field.

<AutoForm
  fieldConfig={{
    username: {
      description:
        "Enter a unique username. This will be shown to other users.",
    },
  }}
/>

You can use JSX in the description.

Custom parent component

You can use the renderParent property to customize the parent element of the input to add adornments etc. By default, this is a React fragment.

<AutoForm
  fieldConfig={{
    username: {
      renderParent: ({ children }) => (
        <div className="flex items-end gap-3">
          <div className="flex-1">
            {children} // This is the input with label etc.
          </div>
          <div>
            <Button type="button">Check</Button>
          </div>
        </div>
      ),
    },
  }}
/>

Accessing the form data

There are two ways to access the form data:

onSubmit

The preferred way is to use the onSubmit prop. This will be called when the form is submitted and the data is valid.

<AutoForm
  onSubmit={(data) => {
    // Do something with the data
    // Data is validated and coerced with zod automatically
  }}
/>

Controlled form

You can also use the values and onValuesChange props to control the form data yourself.

const [values, setValues] = useState<Partial<z.infer<typeof formSchema>>>({});

<AutoForm values={values} onValuesChange={setValues} />;

Please note that the data is not validated or coerced when using this method as they update immediately.

Alternatively, you can use onParsedValuesChange to get updated values only when the values can be validated and parsed with zod:

const [values, setValues] = useState<z.infer<typeof formSchema>>({});

<AutoForm values={values} onParsedValuesChange={setValues} />;

Submitting the form

You can use the AutoFormSubmit component to create a submit button.

<AutoForm>
  <AutoFormSubmit>Send now</AutoFormSubmit>
</AutoForm>
// or
<AutoForm>
  <button type="submit">Send now</button>
</AutoForm>

Adding other elements

All children passed to the AutoForm component will be rendered below the form.

<AutoForm>
  <AutoFormSubmit>Send now</AutoFormSubmit>
  <p className="text-gray-500 text-sm">
    By submitting this form, you agree to our{" "}
    <a href="#" className="text-primary underline">
      terms and conditions
    </a>
    .
  </p>
</AutoForm>

Dependencies

AutoForm allows you to add dependencies between fields to control fields based on the value of other fields. For this, a dependencies array can be passed to the AutoForm component.

<AutoForm
  dependencies={[
    {
      // "age" hides "parentsAllowed" when the age is 18 or older
      sourceField: "age",
      type: DependencyType.HIDES,
      targetField: "parentsAllowed",
      when: (age) => age >= 18,
    },
    {
      // "vegetarian" checkbox hides the "Beef Wellington" option from "mealOptions"
      // if its not already selected
      sourceField: "vegetarian",
      type: DependencyType.SETS_OPTIONS,
      targetField: "mealOptions",
      when: (vegetarian, mealOption) =>
        vegetarian && mealOption !== "Beef Wellington",
      options: ["Pasta", "Salad"],
    },
  ]}
/>

The following dependency types are supported:

  • DependencyType.HIDES: Hides the target field when the when function returns true
  • DependencyType.DISABLES: Disables the target field when the when function returns true
  • DependencyType.REQUIRES: Sets the target field to required when the when function returns true
  • DependencyType.SETS_OPTIONS: Sets the options of the target field to the options array when the when function returns true

The when function is called with the value of the source field and the value of the target field and should return a boolean to indicate if the dependency should be applied.

Please note that dependencies will not cause the inverse action when returning false - for example, if you mark a field as required in your zod schema (i.e. by not explicitly setting optional), returning false in your REQURIES dependency will not mark it as optional. You should instead use zod's optional method to mark as optional by default and use the REQURIES dependency to mark it as required when the dependency is met.

Please note that dependencies do not have any effect on the validation of the form. You should use zod's refine method to validate the form based on the value of other fields.

You can create multiple dependencies for the same field and dependency type - for example to hide a field based on multiple other fields. This will then hide the field when any of the dependencies are met.

Contributing

Contributions are welcome! Please open an issue or submit a pull request.

  1. Fork the repository
  2. Clone your fork and install dependencies with npm install
  3. Run npm run dev to start the development server and make your changes
  4. Run npm run fix to run the formatter and linter
  5. Run npm test to run the tests
  6. Commit your changes and open a pull request

Adding new components

If you want to add a new component, please make sure to add it to the INPUT_COMPONENTS object in auto-form/config.tsx.

  1. Create a new component in src/components/ui/auto-form/fields. You can copy an existing component (like input.tsx) as a starting point.
  2. Add the component to the INPUT_COMPONENTS object in auto-form/config.tsx to give it a name.
  3. Optionally, add the component name as a default handler for a zod type in auto-form/config.tsx under DEFAULT_ZOD_HANDLERS.

License

MIT

auto-form's People

Contributors

cipriancaba avatar darkterminal avatar josephbona avatar papsavas avatar slawton-concurrency avatar targerian1999 avatar th3f0r3ign3r avatar tyghaykal avatar vantezzen avatar vinothsubramanian avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

auto-form's Issues

Support nativeEnum

Would be nice if nativeEnums could default to a Select alongside regular enums

String Arrays Support

Hi there,
First of all thanks for the great tool which you developed. For my case, I need AutoForm to support String Arrays. which unfortunately, I couldn't figure out how to do it.

The field is as below:

highlights: z
      .array(
        z
          .string()
          .describe(
            "e.g. Increased profits by 20% from 2011-2012 through viral advertising"
          )
      )
      .describe("Specify multiple accomplishments")
      .optional(),

React.forwardRef()

When I create a form that is in Dialog:
image
When I open Dialog, I get an error/warning in the console:

Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?

image

Do I need to react to it, or can it be ignored?

enum error

use enum problem
The label's for attribute doesn't match any element id. This might prevent the browser from correctly autofilling the form and accessibility tools from working correctly.
To fix this issue, make sure the label's for attribute references the correct id of a form field.

Default value priority

I've hit a situation where I define the values in the schema, which is stored in a static file, but I want to override the default values via fieldConfig.inputProps which I feel should get priority

I would suggest the following change here:

Instead of

if (
        (defaultValue === null || defaultValue === "") &&
        fieldConfig?.[key]?.inputProps
      ) {
        defaultValue = (fieldConfig?.[key]?.inputProps as unknown as any)
          .defaultValue;
      }

I would change this to:

if ((fieldConfig?.[key]?.inputProps as unknown as any)?.defaultValue) {
        defaultValue = (fieldConfig?.[key]?.inputProps as unknown as any)
          .defaultValue
      }

This way the inputProps will get the highest prio and override the schema default value. What do you think @vantezzen ?

Dependencies for nested fields

Hey! I am trying to implement a rather complex form and would like to apply dependencies on nested fields. Here's a sample schema:

 inventory: z
    .array(
      z.object({
        size: z.enum([
          "XXS",
          "XS",
          "S",
          "M",
          "L",
          "XL",
          "XXL",
          "XXXL",
          "Free Size",
          "One Size",
          "Custom",
        ]),
        customSize: z.string().optional(),
        measurements: z.object({
          bust: z.number().optional(),
          waist: z.number().optional(),
        }),
        variant: z.array(
          z.object({
            name: z.string(),
            quantity: z.number(),
            price: z.number(),
          }),
        ),
      }),
    )

What i am trying to do is show and hide the custom size field depending upon the current value of the size select. The docs do not mention how to name the dependencies when dealing with nested fields.

Grateful for any help.

isRequired is not correct

        const isRequired =
          zodInputProps.required ??
          fieldConfigItem.inputProps?.required ??
          false;

  ------------->
       const isRequired =
          zodInputProps.required  ||
          fieldConfigItem.inputProps?.required ||
          false;

a = b ?? c
?? means : if b is null or undefined then c, not include 'false'

reset form

is there a way to reset form value ? or get form instance ?

if object is .optional() auto-form breaks

if i have a form like:

// ❌
const schema = z.object({
  a: z.string(),
  b: z.object({
    x: z.string(),
  }).optional(),
});

autoform breaks, if i remove .optional it works

 // ✅
export const testSchema = z.object({
  a: z.string(),
  b: z.object({
    x: z.string(),
  }),
});

Array values in nested object aren't loaded

The AutoFormArray component has a bug in it's hook usage that leads to arrays inside nested objects to not load values.

Original version:

const { fields, append, remove } = useFieldArray({ control: form.control, name, });

Solution:

const { fields, append, remove } = useFieldArray({ control: form.control, name: path.join(".") });

Side note, @vantezzen i'd love to collaborate with you on this repo (mainly fixing the rough edges i find and adding support for translations) but i'm not the best expert in using git. Feel free to let me know if we can have a talk!

Confirm password does not work

const formSchema = z
	.object({
		password: z.string(),
		confirm: z.string(),
	})
	.refine((data) => data.password === data.confirm, {
		message: 'Passwords must match.',
		path: ['confirm'],
	});
			<AutoForm
				formSchema={formSchema}
				fieldConfig={{
					password: {
						inputProps: {
							type: 'password',
							placeholder: '••••••••',
						},
					},
					confirm: {
						inputProps: {
							type: 'password',
							placeholder: '••••••••',
						},
					},
				}}
			>

Throws this error

image

optional() for enums breaks the app

For example, this would break the app

color: z.enum(['red', 'green', 'blue']).optional()

Resulting in:

TypeError: Cannot read properties of undefined (reading 'map')
// components\ui\auto-form.tsx (409:20) @ map
  407 | </SelectTrigger>
  408 | <SelectContent>
> 409 |   {values.map((value: any) => (
      |          ^
  410 |     <SelectItem value={value} key={value}>
  411 |       {value}
  412 |     </SelectItem>

required_error message never shows

First, thanks for this project.

I don't know if I'm doing something wrong but the required_error message is never shown for me. Native browser error does.

const formSchema = z.object({
        email: z.
            string({
                required_error: "Veuillez renseigner votre e-mail.",
            })
            .email({
                message: "E-mail incorrect.",
            })
    });

iScreen Shoter - Google Chrome - 231016002710

Am I doing something wrong ?

Email format check error works as intended and display the red message.

Select field with combobox implementation

First of all, great work here! You (and shadcn) are making huge difference for React beginners like me to create prototypes.

It will be really cool to have a searchable select (combobox) especially for API use-cases where large number of results can be returned.

onChange not getting fired on any field

Hey! Awesome library, saves a bunch of time.

However, I am facing this issue while I am trying to display the photos selected by the user through a file input.

 fieldConfig={{
                images: {
                    description: "Upload images",
                    fieldType: "file",
                    inputProps: {
                        multiple: true,
                        accept: "image/*",
                        onChange: (e) => {
                            console.log({ e })
                        },
                    }
                },

onChange is not getting fired on this or any other field. How can I fix this?

Creating a name package ?

Have you thought about providing an npm package for auto-form? 😊

Maybe not make all the components available, or maybe make it possible to override them to have custom ones?

Improvement Proposal

Hello,

Just discovered your autoform component and it is great !

May I ask if it would be possible to include a "hidden" optional field.
In my casse, it would be usefull so that the schema can contain information ,but it should be sent in secret by the browser :)

Also, would it be possible to put two inputs on the same row for example? :) (like on the image below )
image

How to catch autoForm errors?

I wanted to display the errors of the messages that I put in the validation in the schema, does anyone know how to do it ?

disabling a field in fieldconfig

Hi, i am using the following config to disable the date field, but it's not working :
fieldConfig={{
date: {
inputProps:{
disabled:true}

},

fieldConfig type safety for nested fields (z.object)

Hi, I have been doing some experiments with this form and have come across this problem:
When I use only 1 level of fields, without any z.object in my schema, the autocomplete and type validation for fieldConfig works fine, but when I use some nested fields and objects in my schema, the autocompletion is lost.
Consider this schema:
`z.object({
group1: z.object({
username: z.string({
required_error: 'Username is required.',
}),

    password: z.string({
        required_error: 'Password is required.',
    }),
}),

});`
If I now try to put set some irrelevant property to fieldConfig, there is no warning:
image

The expected behavior would be to throw a type error, as in the flat schema case:
image

Is it because typescript is not able to correctly resolve the recursion type to FieldConfigItem ?

Also, I was wondering what approach you would recommend for extending the configuration options for the objects. I would like to be able to choose from more rendering components than just Accordion, but am not sure what is the best path to take. The best solution would be something type-safe.

Anyway, thanks for this great idea of schema based form !

Lookup Field ?

Would it be possible to create a select with data from an api?
Something similar to a lookup field.

Uncontrolled to controlled warning

I'm getting this warning in the console. Wondering if there's a fix for this that doesn't involve controlling the field state and passing that in as a value prop? The warning is stating that the field value has an initial uncontrolled state of null or undefined and is changing to a controlled field when the value changes.

Warning: A component is changing an uncontrolled input to be controlled. This is likely caused by the value changing from undefined to a defined value, which should not happen. Decide between using a controlled or uncontrolled input element for the lifetime of the component.

Zod optional field with default value become required

Hi,
I don't kown why i have a optional field in my zod schema when i put a default value for edit action.
My field become required but i don't want that because it's filled by my model value or empty,
so why it's required ?
Am i doing something wrong ?
Thanks

Props must be serializable for client components

This issue occurs in the date-picker ui component at the setDate prop when using it in Next.js 13.4.

"use client"
...
export function DatePicker({ date, setDate }: { date?: Date; setDate: (date?: Date) => void }) {
...

setDate can not be of type (date?: Date) => void

Array support?

Would be nice to support ZodArray type. React Hook Form has a useFieldArray hook to help add, remove, etc.

onValuesChange not working properly for nested objects, and arrays.

I noticed that onValuesChange is not getting called until the nested object is valid. Example:

On page.tsx

"use client"
import { useState } from "react"
import z from "zod"

import { AutoForm } from 'ui';

const schema = z.object({
  field: z.string(),
  objField: z.object({
      nested: z.string(),
      nested2: z.boolean()
   })
});

export default function Page() {
  const [values, setValues] = useState<Partial<z.infer<typeof schema>>>({})

  function updateValues(data: Partial<z.infer<typeof schema>>){
    console.log(values)
    setValues(data)
  }

  return (
    <>
      <AutoForm
        onValuesChange={updateValues}
        values={values}
        formSchema={schema}
      />
    </>
  );
}

When updating nested but not nested2, onValuesChange is not called since nested2 is required. Maybe it's not a bug, but I would like to get track of every change in the form.

[Bug] Deletion doesn't work properly in Array Support

Just tried the Array Support on the demo website.

Screenshot 2023-09-17 at 17 26 08

When I deleted the first entry this happened:

Screenshot 2023-09-17 at 17 29 21

You can see the first entry is this there but the second one was 'delete'. If you look closely you can see it only removed it from the ui not the json underneath. After clicking the add button again it magically reappears.

Screenshot 2023-09-17 at 17 31 17

After testing with 3 entries, it looks like the delete button always (no matter witch entry) deletes the last entry (from the ui only)

setup code formatter

Would advise to setup a formatter such as Prettier

Not having one makes contributing harder.
You either have to receive everyone's personal formatting in each PR, or have to save without formatting

Conditional Logics for Forms

Feature Suggestion

Description

I'd like to propose a new feature that enables Conditional Logics for Forms in the AutoForms component. This feature would allow users to dynamically render fields based on predefined rules within the AutoForms component.

Technical Details

Implementation Suggestions

To implement this feature, we can follow these steps:

  1. Define Condition Rules: Within the AutoForms component, users can define condition rules that specify when a field should be displayed or hidden based on the values of other fields.

  2. Evaluate Conditions: When a user interacts with the form (e.g., by typing in an input field), the AutoForms component will evaluate the condition rules to determine if any fields should be dynamically rendered or hidden.

  3. Dynamic Rendering: If a condition evaluates to true, the corresponding field will be dynamically rendered; if it evaluates to false, the field will be hidden.

  4. React State Management: Use React state (e.g., useState or useReducer) to manage the dynamic rendering of fields based on the condition rules.

  5. Update Form State: Ensure that the form state accurately reflects the values of all visible fields, even if some are dynamically rendered based on conditions.

Additional Notes

This feature would greatly enhance the flexibility and customization options available to users of the AutoForms component. It would allow for more dynamic and context-aware form rendering.

Looking forward to your feedback on this suggestion!

Best regards,

Property 'innerType' does not exist on type 'ZodAnyDef'.

great component. works smoothly locally but but not deploying due to two issues in the auto-form.tsx

  1. Property 'innerType' does not exist on type 'ZodAnyDef'.
  2. Property 'schema' does not exist on type 'ZodAnyDef'.

related to line 62 and 66:

"function getBaseSchema(schema: z.ZodAny): z.ZodAny {
if ("innerType" in schema._def) {

return getBaseSchema(schema._def.innerType as z.ZodAny);

}
if ("schema" in schema._def) {

return getBaseSchema(schema._def.schema as z.ZodAny);

}
return schema;
}"

Building on Next.js 13 using Shadcn Taxonomy. Any ideas how to fix?

Clear form input on submit

How to clear the input after submiting the stuff?

<AutoForm formSchema={formSchema} onSubmit={addTodo}>
    <AutoFormSubmit />
</AutoForm>

const [todos, setTodos] = useState([] as Todo[]);
  const [done, _setDone] = useState(false);
  const [loading, setLoading] = useState(true);

  const loadTodos = () => {
    const storedTodos = JSON.parse(localStorage.getItem("todos") || "[]");
    setTodos(storedTodos);
    setLoading(false);
  };

  useEffect(() => {
    loadTodos(); // Appel de la fonction pour charger les todos depuis le stockage local
  }, []);

  function addTodo(event: TodoFormResult) {
    // Add the new todo to the list
    const newTodo = { text: event.todo, done, id: Date.now() };
    const updatedTodos = [...todos, newTodo];
    setTodos(updatedTodos);

    // Save the updated list to local storage
    localStorage.setItem("todos", JSON.stringify(updatedTodos));
  }

Select Component eum with custom data limitation

Thanks first of all for creating this awesome project!
While trying to integrate autoform into a project of mine, I came across a limitation of the select field.

The Problem

I have some data from a api that gets fetched on pageload with custom data.
It looks something like this:

const users = [{
id: '123',
name: 'hello'
},
{
id: '789,
name: 'world'
}]

I want to display the name of the field in the select menu, and when submitting it should pass the id.

label = name
value = id

In the docs I found this sentence:

// Keep in mind that zod will validate and return the enum labels, not the enum values!

I am not sure why this is, but this seems very unintuitive, especially when working with values that are not just simple 'one value' enums.

Solution?

Is there a way to work around this? Or even improve the Code to allow for the value to be validated not the label?
Or using a different (custom) datatype, an array of objects with label & value attributes for example

License Addition

📄 License Addition

Description

This repository currently doesn't have a license. Adding a license is essential to clearly define the terms under which the code can be used, modified, or distributed.

Suggested Solution

  1. Determine the most appropriate license for this project. Some popular options include MIT, GPL, and Apache 2.0.
  2. Add the selected license file to the root of the repository.
  3. Update the README to reference the license.

Additional Context

A clear license can encourage contributions by providing clarity on code usage and distribution. Without a license, users and contributors might be hesitant to interact with the project due to legal ambiguities.

Please let me know your thoughts or if there's a specific license you have in mind for this project.

feature request: introduce fieldRender

Alternatively, you can pass a React component to the fieldType property to use a custom component.

overloading fieldType to render the component when you pass a function is not very nice
ideally we could have fieldRender or similar.
I know naming is hard, but this Render suffix is well established naming pattern. Also overloaded fields often give weird typescript errors.

Cannot load array default value

Hi, i have a problem to load array default value. My code like this:

datevisits: z
      .array(
        z.object({
          date: z.date().optional(),
          time: z
            .string()
            .regex(
              /^([0-1]?[0-9]|2[0-3]):[0-5][0-9]$/,
              "Time must be in format HH:MM (24 hours format)"
            )
            .optional(),
        })
      )
      .describe("Date Visits")
      .default(data.date_visits || []),

the data.date_visits will have value like this:

data.data.date_visits = data.data.date_visits.map((item: any) => {
            return {
              date: new Date(item.date),
              time: moment(item.date).format("HH:mm"),
            };
          });

The page loaded, but the date visits value are not set, and when i try to add date visits, date and string field are vanished.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.