NewComplaintForm Component
Overview
The NewComplaintForm component is a React-based form interface for submitting new complaints within an educational institution's complaint management system. It provides a comprehensive form with validation, file uploads, and real-time feedback to users.
Component Structure
The component is built using React with hooks for state management and side effects. It integrates with React Query for data mutations and includes features like drag-and-drop file uploads and form validation.
Dependencies
import React, { useState, useRef, useEffect } from "react";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { toast } from "react-hot-toast";
import convertImageToBase64 from "../../utils/convertImageToBase64";
- React and its hooks for component structure and state management
- React Query for handling data mutations
- react-hot-toast for displaying notification messages
- A utility function to convert images to base64 format for submission
Props
The component accepts the following props:
category: The main category of the complaintsubCategory: The specific subcategory of the complaintonBack: Callback function to return to the previous screen
State Management
const [title, setTitle] = useState("");
const [complaint, setComplaint] = useState("");
const [phoneNumber, setPhoneNumber] = useState("");
const [timeAvailability, setTimeAvailability] = useState("");
const [locality, setLocality] = useState("");
const [detailedAddress, setDetailedAddress] = useState("");
const [files, setFiles] = useState([]);
const [isSubmitting, setIsSubmitting] = useState(false);
const [characterCount, setCharacterCount] = useState(0);
const [isDragging, setIsDragging] = useState(false);
The component maintains several state variables to track form inputs, file uploads, and UI states:
- Form field states for each input
- Files array for tracking uploaded images
- Submission state for disabling controls during submission
- Character count for the description field
- Drag state for the file upload area
Refs
const fileInputRef = useRef(null);
const dropZoneRef = useRef(null);
const dragCounter = useRef(0);
fileInputRef: References the hidden file input elementdropZoneRef: References the drop zone area for drag-and-dropdragCounter: Tracks drag events to handle nested elements
Data Mutation
The component uses React Query's mutation hook to handle form submission:
const mutation = useMutation({
mutationFn: submitComplaint,
onSuccess: () => {
toast.success("Complaint submitted successfully!");
handleClear();
queryClient.invalidateQueries(["complaints"]);
},
onError: (err) => {
toast.error(`Error: ${err.message}`);
},
onSettled: () => {
setIsSubmitting(false);
}
});
This setup:
- Calls the submitComplaint function when triggered
- Shows success toast and clears the form on success
- Invalidates the complaints query to refresh the list
- Shows error toast on failure
- Resets the submitting state when completed
Form Submission
const submitComplaint = async (formData) => {
const accessToken = localStorage.getItem("accessToken");
const res = await fetch("http://localhost:8000/api/complaints/create", {
method: "POST",
headers: {
"Content-Type": "application/json",
authorization: `${accessToken}`,
},
body: JSON.stringify(formData),
credentials: "include",
});
const data = await res.json();
if (!res.ok) {
throw new Error(data?.message || "Failed to submit complaint");
}
onBack(); // Call the onBack function to navigate back to the previous page
return data;
};
The submission function: - Retrieves the authentication token - Makes a POST request to the complaints creation endpoint - Handles response validation - Navigates back on success or throws an error
Form Validation
The component implements several validation checks before submission:
// Basic validation for required fields
if (!title || !complaint || !phoneNumber || !timeAvailability || !locality || !detailedAddress) {
toast.error("All fields are required!");
setIsSubmitting(false);
return;
}
// Phone number validation
const phoneRegex = /^\+?\d{1,4}[\s-]?\d{10}$/;
if (!phoneRegex.test(phoneNumber)) {
toast.error("Please enter a valid phone number (e.g., +91 9876543210)");
setIsSubmitting(false);
return;
}
These validations ensure: - All required fields are filled - Phone number follows the expected format - Time availability is selected
File Upload Handling
The component supports both traditional file input and drag-and-drop for image uploads:
const handleFileChange = (e) => {
const newFiles = Array.from(e.target.files);
processFiles(newFiles);
};
const processFiles = (newFiles) => {
// Filter valid formats and size ( /\.(jpe?g)$/i.test(file.name) && file.size !(/\.(jpe?g)$/i.test(file.name) && file.size 0) {
toast.error("Some files were rejected. Make sure they are JPG format and under 200KB.");
}
if (files.length + validFiles.length > 5) {
toast.error("You can only upload a maximum of 5 files.");
return;
}
setFiles((prevFiles) => [...prevFiles, ...validFiles]);
};
This implementation: - Accepts files from input or drop events - Validates file format (JPG only) and size (max 200KB) - Limits the total number of files to 5 - Provides error feedback for invalid files
Drag and Drop Implementation
const handleDragEnter = (e) => {
e.preventDefault();
e.stopPropagation();
dragCounter.current++;
if (e.dataTransfer.items && e.dataTransfer.items.length > 0) {
setIsDragging(true);
}
};
const handleDragLeave = (e) => {
e.preventDefault();
e.stopPropagation();
dragCounter.current--;
if (dragCounter.current === 0) {
setIsDragging(false);
}
};
const handleDragOver = (e) => {
e.preventDefault();
e.stopPropagation();
if (!isDragging) {
setIsDragging(true);
}
};
const handleDrop = (e) => {
e.preventDefault();
e.stopPropagation();
setIsDragging(false);
dragCounter.current = 0;
// Get the dropped files
if (e.dataTransfer.files && e.dataTransfer.files.length > 0) {
const droppedFiles = Array.from(e.dataTransfer.files);
processFiles(droppedFiles);
e.dataTransfer.clearData();
}
};
This implementation: - Prevents default browser behavior for drag events - Uses a counter to handle nested elements - Updates the UI state to show active dragging - Processes dropped files through the same validation as the file input
UI Features
Character Counter
const handleDescriptionChange = (e) => {
const text = e.target.value;
setCharacterCount(text.length);
setComplaint(text);
};
This feature: - Tracks the number of characters in the description - Changes color when approaching the limit (400 characters) - Provides visual feedback to the user
Image Preview
{files.length > 0 && (
Uploaded Images ({files.length}/5):
{files.map((file, index) => (
removeFile(index)}
className="absolute -top-2 -right-2 bg-red-500 text-white rounded-full p-1 shadow-md hover:bg-red-600 focus:outline-none"
>
{file.name} ({(file.size / 1024).toFixed(1)} KB)
))}
)}
This section: - Displays previews of uploaded images in a grid - Shows file name and size information - Provides a remove button for each image - Indicates the current count out of the maximum allowed
Form Structure
The form is organized into several sections:
- Header: Shows the form title and selected category/subcategory
- Title and Description: Input fields for the complaint title and detailed description
- Contact Information: Fields for phone number and preferred visit time
- Location: Dropdown for locality and text area for detailed address
- Image Upload: Drag-and-drop area and file input for image attachments
- Image Preview: Grid display of uploaded images with remove functionality
- Action Buttons: Submit and Clear buttons with appropriate states
Usage
This component should be rendered when a user selects a category and subcategory for a new complaint. It handles the entire submission process, including form validation, file uploads, and API interaction.