Reputation: 1
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:
Component Structure:
<input type="file" multiple>
with a ref
and a state (selectedFiles
) that tracks the selected files to display previews.Form Submission:
Unexpected Behavior:
<input type="file">
) is reset (its value is cleared).selectedFiles
state remains unchanged, causing image previews to still appear.Security Constraints:
fileInputRef.current.files
isn't possible due to security restrictions.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"
>
×
</button>
</div>
))}
</div>
)}
</>
);
}
Problem Explanation:
When the form is submitted and validation fails:
fileInputRef.current.value = ""
), effectively resetting the file input.selectedFiles
state remains unchanged, so image previews are still displayed.This leads to a mismatch where:
What I've Tried:
Resetting the File Input:
fileInputRef.current.value = ""
to clear the file input upon form submission.Clearing the Selected Files State:
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:
useState
, useEffect
, and useRef
.fileInputRef.current.files
due to security restrictions.Upvotes: 0
Views: 127