Hello everyone, I am trying to implement dynamic forms with directus, it happens that this gives me the guidelines to define the form, ie, what kind of interface expected, value, validations, etc., it would be a question only to generate the schema validations, and interfaces in next js, however I get a little lost with the fact of how the fields are recorded and how to apply the validations, does anyone have any practical example of this or similar?
I leave my code as an example
and as a note, the fact that my problem goes more with the custom interfaces, that is to say with the interfaces provided by Shadcn UI goes great.
"use client"
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { ControllerRenderProps, useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { DirectusField, createItem, readItem, updateItem } from '@directus/sdk';
import { Schema, z } from 'zod';
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form';
import { Button } from '@/components/ui/button';
import { Input } from './_type_fields/_input/input';
import { CalendarComponent } from './_type_fields/_datetime/calendar';
import { SelectDropdownComponent } from './_type_fields/_select_dropdown/select';
import { transformToTitleCase } from '@/lib/utils';
import directus, { handleError } from '@/lib/directus';
import { ApiCollections, components } from '../../../../../types/api-collection';
import { toast } from '@/components/ui/use-toast';
import { useRouter } from 'next/navigation';
import { SelectDropdownM2OComponent } from './_type_fields/_select_dropdown_m2o/select-dropdown-m2o';
import { TextareaComponent } from './_type_fields/_textarea/textarea';
import { FilesComponent } from './_type_fields/_files/filesComponent';
const getSchemaForFieldType = (field: DirectusField): z.ZodType<any, any> | null => {
switch (field.type) {
case 'string':
case 'text':
case 'hash':
const stringSchema = z.coerce.string();
if (field.schema?.max_length) {
stringSchema.max(field.schema.max_length);
}
return stringSchema;
case 'boolean':
return z.boolean();
case 'integer':
case 'bigInteger':
const numericSchema = z.coerce.number({
required_error: "Required",
invalid_type_error: "Must be a number",
}).int({ message: "Must be an integer" });
if (field.schema?.numeric_precision) {
numericSchema.min(field.schema.numeric_precision);
}
if (field.schema?.numeric_scale) {
numericSchema.max(field.schema.numeric_scale);
}
return numericSchema;
case 'float':
case 'decimal':
const decimalSchema = z.coerce.number({
required_error: "Required",
invalid_type_error: "Must be a number",
});
if (field.schema?.numeric_precision) {
decimalSchema.min(field.schema.numeric_precision);
}
if (field.schema?.numeric_scale) {
decimalSchema.max(field.schema.numeric_scale);
}
return decimalSchema;
case 'timestamp':
case 'dateTime':
case 'date':
case 'time':
const dateSchema = z.coerce.date();
if (field.schema?.is_nullable) {
dateSchema.nullable();
}
return dateSchema;
case 'json':
return z.object({ / Schema JSON / });
case 'csv':
return z.array(z.string());
case 'uuid':
const uuidSchema = z.string().uuid();
return uuidSchema;
case 'binary':
return z.any();
case 'alias':
return null;
default:
return null;
}
};
This issue has been automatically closed because it received no activity for a while. If you think it was closed by accident, please leave a comment. Thank you.
Hello everyone, I am trying to implement dynamic forms with directus, it happens that this gives me the guidelines to define the form, ie, what kind of interface expected, value, validations, etc., it would be a question only to generate the schema validations, and interfaces in next js, however I get a little lost with the fact of how the fields are recorded and how to apply the validations, does anyone have any practical example of this or similar?
I leave my code as an example and as a note, the fact that my problem goes more with the custom interfaces, that is to say with the interfaces provided by Shadcn UI goes great.
"use client" import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { ControllerRenderProps, useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { DirectusField, createItem, readItem, updateItem } from '@directus/sdk'; import { Schema, z } from 'zod'; import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form'; import { Button } from '@/components/ui/button'; import { Input } from './_type_fields/_input/input'; import { CalendarComponent } from './_type_fields/_datetime/calendar'; import { SelectDropdownComponent } from './_type_fields/_select_dropdown/select'; import { transformToTitleCase } from '@/lib/utils'; import directus, { handleError } from '@/lib/directus'; import { ApiCollections, components } from '../../../../../types/api-collection'; import { toast } from '@/components/ui/use-toast'; import { useRouter } from 'next/navigation'; import { SelectDropdownM2OComponent } from './_type_fields/_select_dropdown_m2o/select-dropdown-m2o'; import { TextareaComponent } from './_type_fields/_textarea/textarea'; import { FilesComponent } from './_type_fields/_files/filesComponent';
const getSchemaForFieldType = (field: DirectusField): z.ZodType<any, any> | null => {
switch (field.type) {
case 'string':
case 'text':
case 'hash':
const stringSchema = z.coerce.string();
if (field.schema?.max_length) {
stringSchema.max(field.schema.max_length);
}
return stringSchema;
case 'boolean':
return z.boolean();
case 'integer':
case 'bigInteger':
const numericSchema = z.coerce.number({
required_error: "Required",
invalid_type_error: "Must be a number",
}).int({ message: "Must be an integer" });
if (field.schema?.numeric_precision) {
numericSchema.min(field.schema.numeric_precision);
}
if (field.schema?.numeric_scale) {
numericSchema.max(field.schema.numeric_scale);
}
return numericSchema;
case 'float':
case 'decimal':
const decimalSchema = z.coerce.number({
required_error: "Required",
invalid_type_error: "Must be a number",
});
if (field.schema?.numeric_precision) {
decimalSchema.min(field.schema.numeric_precision);
}
if (field.schema?.numeric_scale) {
decimalSchema.max(field.schema.numeric_scale);
}
return decimalSchema;
case 'timestamp':
case 'dateTime':
case 'date':
case 'time':
const dateSchema = z.coerce.date();
if (field.schema?.is_nullable) {
dateSchema.nullable();
}
return dateSchema;
case 'json':
return z.object({ / Schema JSON / });
case 'csv':
return z.array(z.string());
case 'uuid':
const uuidSchema = z.string().uuid();
return uuidSchema;
case 'binary':
return z.any();
case 'alias':
return null;
default:
return null;
}
};
const getDefaultForFieldType = (field: DirectusField): any => {
return field.schema?.default_value != undefined ? field.schema?.default_value : '';
};
async function getItem(collectionName: string, id: string) { try { let data = await directus.request(readItem(collectionName as keyof ApiCollections, id)) as unknown as ApiCollections[keyof ApiCollections][]; return data; } catch (error: any) { console.log(error); handleError(error); return [] } }
const createFormSchemaAndDefaults = (fields: DirectusField[], item: { [key: string]: any } | null) => {
const schemaObject: { [key: string]: z.ZodType<any, any> } = {};
const defaultValues: { [key: string]: any } = {};
};
const saveItem = async (data: { [key: string]: any }, formId: string) => { try { await directus.request( createItem(formId as keyof ApiCollections, data) ); toast({ title: "Success", description: "Item created" }); return true; } catch (error: any) { handleError(error); return false; } };
const updateAnItem = async (data: { [key: string]: any }, formId: string, idItem: string | number) => { try { await directus.request( updateItem(formId as keyof ApiCollections, idItem, data) ); toast({ title: "Success", description: "Item updated" }); return true; } catch (error: any) { handleError(error); return false; } };
export default function DynamicFormContainer({ fields, formId, action }: { fields: DirectusField[], formId: string, action: string }) {
if (!fields || fields.length === 0) {
return Loading... ;
}
}
function DynamicForm({ fields, formId, action }: { fields: DirectusField[], formId: string, action: string }) {
const [loading, setLoading] = useState(true);
const [formData, setFormData] = useState<{ formSchema: z.ZodTypeAny; defaultValues: { [key: string]: any } }>();
const router = useRouter();
}
const renderInputComponent = (fieldData: DirectusField<Schema<any, z.ZodTypeDef, any>>, field: ControllerRenderProps<{ [key: string]: any }, string>) => { switch (fieldData.meta.interface) { case 'input': return fieldData.type === 'integer' || fieldData.type === 'bigInteger' || fieldData.type === 'float' || fieldData.type === 'decimal' ? ( <Input {...field} {...fieldData} type='number' /> ) : ( <Input {...field} {...fieldData} /> ); case 'datetime': return <CalendarComponent {...field} field={fieldData} /> case 'select-dropdown': return <SelectDropdownComponent {...field} {...fieldData} /> case 'select-dropdown-m2o': return <SelectDropdownM2OComponent {...field} field={fieldData} /> case 'input-rich-text-md': return <TextareaComponent {...field} /> case 'files': return
default:
return null;
}
};