DADDA Hamza
DADDA Hamza

Reputation: 1

Next.js Form with Server Actions: Reset File Input and Preview State on Validation Error

I'm developing a Next.js 15 web application where I've implemented a form using server actions to handle data submission. The form is modularized into multiple components based on functionality, including:

Issue:

The problem arises with the Image Upload Component. Here's what's happening:

  1. Component Structure:

    • The component contains an <input type="file" multiple> with a ref and a state (selectedFiles) that tracks the selected files to display previews.
  2. Form Submission:

    • When the user submits the form, a server action validates the data.
    • If validation fails, the form displays error messages.
  3. Unexpected Behavior:

    • Upon clicking the submit button:
      • The file input (<input type="file">) is reset (its value is cleared).
      • However, the selectedFiles state remains unchanged, causing image previews to still appear.
    • This inconsistency confuses users because:
      • The form indicates that no images are uploaded (since the file input is empty).
      • Yet, image previews are still visible.
  4. Security Constraints:

    • Directly setting fileInputRef.current.files isn't possible due to security restrictions.
    • Server actions require that the file input contains data to process the uploaded files.

Relevant Code Snippets:

1. FormWrapper Component:

"use client";

import { saveOrder, State } from "../utils/saveOrderAction";
import { useActionState } from "react";
import { useEffect, useState } from "react";
import CustomTransition from "@/components/Transition";
import SubmitButton from "@/components/SubmitButton";
import { useRouter } from "next/navigation";

interface FormWrapperProps {
  children: React.ReactNode;
}

export default function FormWrapper({ children }: FormWrapperProps) {
  const [errorMessages, setErrorMessages] = useState<string[]>([]);
  const router = useRouter();
  const [state, formAction, pending] = useActionState<State, FormData>(
    saveOrder,
    null
  );

  // Get error messages
  useEffect(() => {
    if (state?.status === "failure" && state.errors) {
      const errors = Object.values(state.errors).filter(Boolean) as string[];
      setErrorMessages(errors);
    } else {
      setErrorMessages([]);
    }
  }, [state]);

  // Redirect after success
  useEffect(() => {
    if (!pending && state?.status === "success") {
      setTimeout(() => {
        router.replace("/");
      }, 300);
    }
  }, [pending, state]);

  return (
    <form
      action={formAction}
      className="max-w-2xl mx-auto p-6 bg-white rounded-lg shadow-md"
    >
      {/* Form Fields */}
      {children}

      {/* Submit Button with Loading Spinner */}
      <div className="mt-6 flex items-center">
        <SubmitButton pending={pending} />
      </div>

      {/* Error Alert with Transition */}
      <CustomTransition show={errorMessages.length > 0}>
        <div className="my-4 p-4 bg-red-100 border border-red-400 text-red-700 rounded">
          <h2 className="font-bold mb-2">
            There were some errors with your submission:
          </h2>
          <ul className="list-disc list-inside">
            {errorMessages.map((error, index) => (
              <li key={index}>{error}</li>
            ))}
          </ul>
        </div>
      </CustomTransition>

      {/* Success Message with Transition */}
      <CustomTransition show={state?.status === "success"}>
        <div className="mt-4 p-4 bg-green-100 border border-green-400 text-green-700 rounded">
          Your order has been successfully placed!
        </div>
      </CustomTransition>
    </form>
  );
}

2. FormImages Component:

"use client";

import { ChangeEvent, useRef, useState, useEffect } from "react";

export default function FormImages({ resetTrigger }: { resetTrigger: boolean }) {
  const [selectedFiles, setSelectedFiles] = useState<File[]>([]);
  const fileInputRef = useRef<HTMLInputElement | null>(null);

  const handleFileChange = (e: ChangeEvent<HTMLInputElement>) => {
    if (e.target.files) {
      setSelectedFiles(Array.from(e.target.files));
    }
  };

  const handleDeleteFile = (fileIndex: number) => {
    setSelectedFiles((prevFiles) =>
      prevFiles.filter((_, index) => index !== fileIndex)
    );
    if (fileInputRef.current) {
      fileInputRef.current.value = "";
    }
  };

  // Reset selected files when resetTrigger changes
  useEffect(() => {
    if (resetTrigger) {
      setSelectedFiles([]);
      if (fileInputRef.current) {
        fileInputRef.current.value = "";
      }
    }
  }, [resetTrigger]);

  return (
    <>
      <label className="block mb-4">
        <span className="text-gray-600">
          Upload images of the product you're ordering
        </span>
        <input
          type="file"
          name="selectedFiles"
          accept="image/*"
          required
          multiple
          onChange={handleFileChange}
          ref={fileInputRef}
          className="mt-1 block w-full p-2 border rounded border-gray-300"
        />
      </label>
      {selectedFiles.length > 0 && (
        <div className="grid grid-cols-3 gap-4">
          {selectedFiles.map((file, index) => (
            <div key={index} className="relative">
              <img
                src={URL.createObjectURL(file)}
                alt="Uploaded Preview"
                className="h-32 w-full object-cover rounded-lg"
              />
              <button
                type="button"
                onClick={() => handleDeleteFile(index)}
                className="absolute top-0 right-0 bg-red-500 text-white rounded-full p-1"
                aria-label="Delete image"
              >
                &times;
              </button>
            </div>
          ))}
        </div>
      )}
    </>
  );
}

Problem Explanation:

When the form is submitted and validation fails:

This leads to a mismatch where:

What I've Tried:

  1. Resetting the File Input:

    • Used fileInputRef.current.value = "" to clear the file input upon form submission.
  2. Clearing the Selected Files State:

    • Attempted to manually clear the selectedFiles state when validation errors occur.

Expected Behavior:

Upon form submission with validation errors:

Question:

How can I ensure that both the file input and the selectedFiles state are populated with the same data when the form submission results in validation errors using Next.js server actions? I'm looking for a way to stop input from resetting when form validation errors occur and if that' snot possible synchronize the resetting of the file input with the component's state to prevent confusion caused by lingering image previews.

Additional Information:

Upvotes: 0

Views: 127

Answers (0)

Related Questions