Skip to content

Instantly share code, notes, and snippets.

@izoukhai
Last active November 1, 2023 15:47
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save izoukhai/6cd19c86fa78cd1b6f70826ef6c4bfab to your computer and use it in GitHub Desktop.
Save izoukhai/6cd19c86fa78cd1b6f70826ef6c4bfab to your computer and use it in GitHub Desktop.
Generate forms from Directus CMS and render them with NextJS
"use client";
import { FieldValues, UseFormProps, UseFormReturn } from "react-hook-form";
import { ApiCollections } from "../../../@types/api";
export const DirectusInput = (props: {
element: ApiCollections["form_components"];
hookForm: UseFormReturn<FieldValues, any>;
}) => {
const { element, hookForm } = props;
const { register } = hookForm;
return (
<input
required={element.required || false}
{...element.component_props}
{...register(element.key!, {
required: element.required || false,
})}
/>
);
};
"use client";
import { FieldValues, UseFormProps, UseFormReturn } from "react-hook-form";
import { ApiCollections } from "../../../@types/api";
export const DirectusSelect = (props: {
element: ApiCollections["form_components"];
hookForm: UseFormReturn<FieldValues, any>;
}) => {
const { element, hookForm } = props;
const { register } = hookForm;
return (
<select
required={element.required || false}
{...element.component_props}
{...register(element.key!, {
required: element.required || false,
})}
>
{element.component_props?.placeholder && (
<option value="" hidden selected disabled>
{element.component_props.placeholder}
</option>
)}
{(element.choices as { label: string; value: any }[])?.map((res, key) => (
<option value={res.value} key={`select-${element.key}-option-${key}`}>
{res.label}
</option>
))}
</select>
);
};
"use client";
import { FieldValues, UseFormProps, UseFormReturn } from "react-hook-form";
import { ApiCollections } from "../../../@types/api";
export const DirectusTextarea = (props: {
element: ApiCollections["form_components"];
hookForm: UseFormReturn<FieldValues, any>;
}) => {
const { element, hookForm } = props;
const { register } = hookForm;
return (
<textarea
required={element.required || false}
{...element.component_props}
{...register(element.key!, {
required: element.required || false,
})}
/>
);
};
import { RenderDirectusForm } from "../../components/DirectusForm";
import { directus } from "../../lib/directus";
const getForm = async () => {
return await directus
.items("forms")
.readOne("f9b8c1ea-de27-4458-a90c-92b204e76ee1", {
fields: ["*", "elements.*"],
})!;
};
const FormBuilder = async () => {
const form = await getForm();
return (
<div className="container min-h-screen max-w-screen-xl">
<div className="mx-auto w-1/2">
<RenderDirectusForm form={form!} />
</div>
</div>
);
};
export default FormBuilder;
"use client";
import React from "react";
import { UseFormReturn, FieldValues } from "react-hook-form";
import { ApiCollections } from "../../../@types/api";
import { DirectusInput } from "./DirectusInput";
import { DirectusSelect } from "./DirectusSelect";
import { DirectusTextarea } from "./DirectusTextarea";
export const RenderDirectusComponent = (props: {
element: ApiCollections["form_components"];
hookForm: UseFormReturn<FieldValues, any>;
}) => {
const { element, hookForm } = props;
switch (element.type) {
case "input":
return <DirectusInput element={element} hookForm={hookForm} />;
case "textarea":
return <DirectusTextarea element={element} hookForm={hookForm} />;
case "select":
return <DirectusSelect element={element} hookForm={hookForm} />;
default:
return <React.Fragment />;
}
};
"use client";
import { useMutation } from "@tanstack/react-query";
import { useRouter } from "next/navigation";
import { useForm } from "react-hook-form";
import { twMerge } from "tailwind-merge";
import { ApiCollections } from "../../@types/api";
import { directus } from "../../lib/directus";
import { Button } from "../UI/Button";
import { RenderDirectusComponent } from "./components";
const RenderSuccessMessage = (props: { message: string }) => {
return <p>{props.message}</p>;
};
export const RenderDirectusForm = (props: {
form: ApiCollections["forms"];
}) => {
const router = useRouter();
const hookForm = useForm();
const { mutate, isLoading, isError, isSuccess } = useMutation(
async (elements: Record<string, any>) =>
await directus.items("form_submits").createOne({
form: props.form.id,
elements,
}),
{
onSuccess: () => {
if (props.form.on_success === "redirect") {
router.push(props.form.on_success_redirect_link || "/");
}
},
}
);
const onSubmit = (data: Record<string, any>) => {
mutate(data);
};
return isSuccess && props.form.on_success === "show_message" ? (
<RenderSuccessMessage
message={props.form.on_success_message || "Thanks for submitting!"}
/>
) : (
<form
className="grid grid-cols-12 gap-5"
onSubmit={hookForm.handleSubmit(onSubmit)}
>
<p className="col-span-12 text-center text-xl font-bold">
{props.form.name}
</p>
{(props.form?.elements as ApiCollections["form_components"][])?.map(
(res) => (
<div
key={`directusComponent-${res}`}
className={twMerge(`col-span-12`, `md:col-span-${res.col_span}`)}
>
<p className="mb-1">{res.label}</p>
<RenderDirectusComponent element={res} hookForm={hookForm} />
</div>
)
)}
<div className="col-span-12 mx-auto">
<Button type="submit" isLoading={isLoading}>
Submit
</Button>
</div>
</form>
);
};
Visit https://www.izoukhai.com for more information
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment