I am using Nextjs14 and Supabase.
I have this select
where the user can choose from. I am also passing it on the formValue
which will be received on the server actions as FormData
. Now, on the component, I can see the data of the formValue
and the barangay
field is there. However, passing in on the server actions, the barangay
field can no longer be seen on the server actions.
This is my reusable component named DropDownList.tsx
:
import React, { Fragment, useState, useEffect } from 'react';
import { Listbox, Transition } from '@headlessui/react';
import { CheckIcon, ChevronUpDownIcon } from '@heroicons/react/20/solid';
type Props = {
options: string[];
selected: string;
onSelect: (value: string) => void;
required: boolean;
placeholder: string;
value: string;
name: string;
};
function classNames(...classes: string[]) {
return classes.filter(Boolean).join(' ');
}
const DropdownList: React.FC<Props> = ({ options, selected, onSelect, required, placeholder , value, name}) => {
// Initialize the selected state with the default selected value from the parent
const [currentSelected, setCurrentSelected] = useState<string | null>(selected);
// Update the selected state when the selected prop changes
useEffect(() => {
setCurrentSelected(selected);
}, [selected]);
return (
<Listbox value={currentSelected} onChange={(value) => {
if (value !== null) {
setCurrentSelected(value);
onSelect(value);
}
}}>
{({ open }) => (
<>
<Listbox.Label className="block text-sm font-medium leading-6 text-gray-900">
Select an option
</Listbox.Label>
<div className="relative mt-2">
<Listbox.Button className={`relative w-full cursor-default rounded-md bg-white py-1.5 pl-3 pr-10 text-left text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 focus:outline-none focus:ring-2 focus-ring-indigo-500 sm:text-sm sm:leading-6 ${required ? 'required' : ''}`}>
<span className="ml-3 block truncate">{currentSelected || placeholder}</span>
<span className="pointer-events-none absolute inset-y-0 right-0 ml-3 flex items-center pr-2">
<ChevronUpDownIcon className="h-5 w-5 text-gray-400" aria-hidden="true" />
</span>
</Listbox.Button>
<Transition
show={open}
as={Fragment}
leave="transition ease-in duration-100"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<Listbox.Options className="absolute z-10 mt-1 max-h-56 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm">
{/* Add a placeholder option */}
<Listbox.Option key="placeholder" value="" disabled>
{({ active }) => (
<span className={classNames('text-gray-500', 'ml-3 block truncate')}>
{placeholder}
</span>
)}
</Listbox.Option>
{options.map((option, i) => (
<Listbox.Option
key={i}
className={({ active }) =>
classNames(
active ? 'bg-indigo-600 text-white' : 'text-gray-900',
'relative cursor-default select-none py-2 pl-3 pr-9'
)
}
value={option}
>
{({ selected, active }) => (
<>
<span className={classNames(selected ? 'font-semibold' : 'font-normal', 'ml-3 block truncate')}>
{option}
</span>
{selected ? (
<span
className={classNames(
active ? 'text-white' : 'text-indigo-600',
'absolute inset-y-0 right-0 flex items-center pr-4'
)}
>
<CheckIcon className="h-5 w-5" aria-hidden="true" />
</span>
) : null}
</>
)}
</Listbox.Option>
))}
</Listbox.Options>
</Transition>
</div>
</>
)}
</Listbox>
);
};
export default DropdownList;
I am using it on this form: On this part, I can see the value barangay
.
'use client'
import addWaterStation from "@/app/auth/actions/WaterStation/addWaterStation";
import React, { useEffect, useState} from "react";
import { useFormState, useFormStatus } from "react-dom";
import DropdownList from "@/components/Reusables/MyDropDownList";
import { barangay } from "./barangay";
interface FormData {
...rest of the values
barangay: string;
}
const initialState = {
message: null,
}
function SubmitButton() {
const { pending } = useFormStatus()
return (
<button type="submit" aria-disabled={pending}>
Submit
</button>
)
}
export default function WaterStationProfileForm() {
const [state, formAction] = useFormState(addWaterStation, initialState)
const [selectedBarangay, setSelectedBarangay] = useState<string>(''); // Rename the state variable
const handleBarangaySelection = (value: string) => {
setSelectedBarangay(value); //get the selected barangay
}
const [formValue, setFormValue] = useState<FormData>({
...rest of the values
barangay: "",
});
useEffect(() => {
setFormValue(prevFormValue => ({
...prevFormValue,
barangay: selectedBarangay
}))
},[selectedBarangay])
console.log(formValue, "form value") // I can see the value of barangay here
return (
<div className="container mx-auto p-4">
<form action={formAction}>
...the rest of the inputs
<DropdownList
options={barangay}
value={formValue.barangay}
selected={selectedBarangay}
onSelect={handleBarangaySelection}
required={true}
placeholder="Please select an option"
name="barangay"
/>
<SubmitButton />
</form>
</div>
);
}
My server action: On this part, the value barangay
is not present anymore. Only the rest of the values shows.
Sample FormData
value. The barangay
is not on the name
and no value:
FormData {
[Symbol(state)]: [
{ name: '$ACTION_REF_1', value: '' },
{
name: '$ACTION_1:0',
value: '{"id":"32933f3a25fdc884c208dbcbf3540cf9ccf38c62","bound":"$@1"}'
},
{ name: '$ACTION_1:1', value: '[{"message":null}]' },
{ name: '$ACTION_KEY', value: 'k3775125042' },
{ name: 'zone', value: '7' },
{ name: 'delivery_mode', value: 'Delivery and Pickup' },
{ name: 'contact_no', value: '9676832484' },
{ name: 'tel_no', value: '' },
{ name: 'remarks', value: '' }
]
} formData
Codes for the server action:
'use server'
import { createServerComponentClient } from "@supabase/auth-helpers-nextjs"
import { revalidatePath } from "next/cache";
import { cookies } from "next/headers"
export default async function addWaterStation(prevState: any, formData: FormData): Promise<{ message: string }> { const supabase =createServerComponentClient({cookies})
const {data: {user}} = await supabase.auth.getUser();
const formDataAddress = `${formData.get('buildingNumber')}, ${formData.get('street')}, ${formData.get('zone')}`
console.log(formData, "formData")
try{
const station_name = formData.get('name')
const {data, error} = await supabase.from('water_refilling_station')
.insert({
...rest of the inputs here
barangay,
}).select()
if(error){
return {message: `${error.message} - unable to save`}
}
revalidatePath('/water_station')
return { message: `Succesfully added the data` }
}catch(e){
return {message: "Failed to submit the form."}
}
}
Put a hidden input somewhere in the <form>
, only input values are passed to the form action.
You can solve it in two ways, either by rendering this beside your DropDownList:
<input type="hidden" name="barangay" value={selectedBarangay} />
or by rendering this inside your DropDownList:
<input type="hidden" name={name} value={selected} />