javascriptreactjstypescriptnext.jssupabase

Value of the select can be seen on the component. But when passing it to the server action, it does no show anymore


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."}
    }

}

Solution

  • 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} />