Server Actions
In Next.js, server actions, (the files started with a little 'use server' at the top), are asynchronous functions that simplify form management. They can be incorporated into both Server and Client Components, promoting code reuse and efficiency. Specifically, 'use server' is used in Server Components, while Client Components can employ Server Actions for form processing.
This can help manage form submissions and related data changes, offering features like pending submissions in Client Components. They also work with Next.js' caching and revalidation system, meaning you have only one trip and all your UI updates and data management is sorted.
FormSpark
Formspark is an innovative solution that allows you to collect and save information from your website forms without setting up a server. If you want two great reasons to use it:
- It's stupidly fast
- It's studidly cheap
Simply, it just takes managing the data bit of forms out of the equation.
Implementing Formspark in Next.js with server action is quite straightforward so we thought we'd throw together a quick fix guide, because in our opinion this is the defacto method of setting up a simple form without breaking out the backend skills.
For all you out there thinking of trying to skip the frontend step as well, we actually built the following contact form from V0.dev. It's basically precisely what this tool was made for.
export const ContactForm: FC = () => {return (<Card className=" shadow-lg max-w-md mx-auto p-6 rounded-lg"><CardHeader><CardTitle className="text-2xl font-bold text-center">Contact Us</CardTitle><CardDescription className="text-gray-500 text-center">We would love to hear from you. Please fill out the form below.</CardDescription></CardHeader><CardContent>{/* Here's the form with the id from formspark */}<Form formId="1MtTF9c1l" /></CardContent></Card>);};
This is the code where all the magic happens we are using React’s new hooks
- useFormState The useFormState hook in React allows for creating and managing component states in response to form actions. It pairs an action function with an initial state, returning a new action and the latest form state for use in forms. This enables dynamic updating of form state and supports server actions for immediate response display. more here
- useFormStatus The useFormStatus hook in React provides status information about the last form submission, such as whether it's pending. It's used within a form component to dynamically enable or disable elements like a submit button based on the form's submission status. This hook returns an object containing properties like pending, data, method, and action to manage form interactions efficiently. more here
const SubmitButton: FC = () => {const { pending } = useFormStatus();return (<Buttondisabled={pending}variant={"secondary"}className="bg-blue-500 text-white w-full flex gap-2">{pending && <Loader2 className="animate-spin text-white" />}Submit</Button>);};const INITIAL = {message: "",hasError: false,isComplete: false,};const Form: FC<FormProps> = ({ formId }) => {const _action = formSparkFormAction.bind(null, formId);const [state, action] = useFormState(_action, INITIAL);if (!state.hasError && state.isComplete)return (<div className="flex w-full max-w-wd" id="form"><div className="my-10 flex w-full items-center justify-center gap-4 rounded-lg bg-gray-200 p-4 text-black "><CheckCircle />Form Submitted</div></div>);if (state.hasError && state.isComplete) {console.error("Form submit error:", state.message);return (<div className="flex w-full max-w-wd" id="form"><div className="my-10 flex w-full items-center justify-center gap-4 rounded-lg bg-red-200 p-4 text-black "><CrossCircledIcon />something went wrong!!</div></div>);}return (<form className="space-y-4" action={action}><div className="grid grid-cols-2 gap-4"><div className="space-y-2"><Label htmlFor="first-name">First name</Label><Inputid="first-name"name="first-name"placeholder="Enter your first name"/></div><div className="space-y-2"><Label htmlFor="last-name">Last name</Label><Inputid="last-name"name="last-name"placeholder="Enter your last name"/></div></div><div className="space-y-2"><Label htmlFor="email">Email</Label><Inputid="email"name="email"placeholder="Enter your email"type="email"/></div><div className="space-y-2"><Label htmlFor="message">Message</Label><TextareaclassName="min-h-[100px]"id="message"name="message"placeholder="Enter your message"/></div><SubmitButton /></form>);};
This code defines two components, SubmitButton and Form.
The SubmitButton enhances the user experience by displaying a loader during the form submission process. Nice
The Form state holds information about possible errors and the status of the form submission. Depending on this state, the component presents different user interfaces: if the form is successfully submitted without errors, a success message is displayed. If there's an error during submission, an error message is shown. When the form is still in the process of being filled out, the actual form is rendered.
"use server";import axios, { isAxiosError } from "axios";export async function formSparkFormAction<T>(formId: string,state: T,form: FormData) {console.log("", formId, state, form);if (!formId) {return {message: "Form field missing",hasError: true,isComplete: false,};}const extractData = Object.fromEntries(form.entries());try {await axios.post(`https://submit-form.com/${formId}`, extractData);} catch (error) {console.log({ error });if (isAxiosError(error)) {console.log(error.response?.data, error.message);return { message: error.message, hasError: true, isComplete: true };}}return { message: "done", hasError: false, isComplete: true };}
This code is a server action function for form submission. It takes three parameters: the form ID, the state of the form, and the form data itself. It extracts the data from the form entries. and sends a POST request to the Formspark URL with the form data. If an error occurs during the request, it logs the error and returns an error message
Here’s the github link for the complete tutorial: link
Outro
We hope you enjoyed the tutorial, and if there's anything that isn't clear, give us a shout. We're always trying to ensure we pass on knowledge in a way that doesn't feel like you're reading a dictionary front to back.