Search code examples
javascriptreactjsdrop-down-menuhtml-selectformik

Adding a default value to a Select list of options, where the options are read from a database


I have a Formik form which contains a field which uses the HTML Select tag to create a drop-down list. The data in this select drop-down comes from an array of data which is read from a MySQL database table by way of a useEffect hook. The fields in that array are staff_id and full_name. The user should see and select a full_name in the drop-down, then when the form is saved to the database, it should save the corresponding staff_id. This functionality is working in the code below.

My problem is that the first row of data from the array is displayed as the default value in the drop-down - e.g. the first name might be Joe Bloggs, and that's what appears when a user first opens the form. If the user tries to save the form at that point, without doing anything to the select drop-down, the form's save button does nothing - I presume because no option has actually been selected in the drop-down, so the 'value' is 'undefined'.

If the user wanted to select Joe Bloggs, they would need to ignore that Joe Bloggs was the displayed default option, select another option from the list, then go back and select Joe Bloggs again.

To prevent this situation, I've seen examples where, when the data is not sourced from a database, but instead an array of options is hardcoded, people add another key:value pair in the list of options, calling it something like "Please choose an option..." and a value of null or zero. That option becomes the default value which displays when the user first opens the form, in turn forcing the user to select a different option.

How would I achieve that same kind of functionality I've seen hardcoded, whilst still populating the options array from the database? Should I be amending another key/value pair to the top of the array that has been returned from the database, containing an option like "Please select from below and value='0'? Or is there some property that I can use to set a default value for the select drop-down list? Or some other way to achieve this that I've not considered?

Code:


    import React, { useState, useEffect } from 'react';
    import { Formik, Form, Field, ErrorMessage } from 'formik';
    import * as Yup from 'yup'; //yup does form validation
    import axios from 'axios';
    import { useMutation } from '@tanstack/react-query';
    import { useRecoilState } from 'recoil'
    import { eventButtonClickedState } from '../Atoms/atoms'
    import Button from '@mui/material/Button'
    // import Select from 'react-select'
    
    //react-query useMutation code
    const useEventsCreateMutation = () => {
      return useMutation((formPayload) => {
        return axios.post('http://localhost:3001/events', formPayload);
      });
    };
    
    //Variable to store Tailwind css for 'Field' elements of Formik function
    const formikField =
      'my-px block px-2.5 pb-2.5 pt-4 w-full text-sm text-gray-900 bg-transparent rounded-lg border border-gray-400 appearance-none focus:outline-none focus:ring-0 focus:border-blue-600 peer';
    
    //Variable to store Tailwind css for 'Label' elements of Formik function
    const formikLabel =
      'absolute text-base text-gray-500 duration-300 transform -translate-y-4 scale-75 top-2 z-10 origin-[0] bg-white dark:bg-gray-900 px-2 peer-focus:px-2 peer-focus:text-blue-600 peer-placeholder-shown:scale-100 peer-placeholder-shown:-translate-y-1/2 peer-placeholder-shown:top-6 peer-focus:top-1 peer-focus:scale-75 peer-focus:-translate-y-4 left-1';
    
    //Main function - creates Formik form
    function EventsCreate() {
      const { mutate } = useEventsCreateMutation();
    
      //Formik initial values (not actually used here)
      const initialValues = {
        event_name: '',
        staff_id: '',
      };
    
      // Yup field validation
      const validationSchema = Yup.object().shape({
        event_name: Yup.string()
        .required('*Event Name is required')
        .max(35, 'Event Name can be a maximum of 35 characters'),
      staff_id: Yup.number()
        .required('*Event Leader is required'),
      });
    
      // State used to display success/error message
      const [createMsg, setCreateMsg] = useState('');
      // console.log(createMsg);
    
      // Recoil global state to trigger data table refresh after event edit button is clicked
      const [buttonisClicked, setButtonIsClicked] = useRecoilState(eventButtonClickedState)
    
      // State for staff data to populate Event Leader dropdown
      const[staff, setStaff] = useState([])
      // console.log(staff)
    
      // Gets array of staff ids/names from staff table
      useEffect(() => {
        axios.get('http://localhost:3001/staff/staffdropdown')
            .then((res) => res.data)
            .then(data => setStaff(data))
      }, [])
    
    
    
      return (
        <div className="createEventPage px-5">
          <Formik
            initialValues={initialValues}
            validationSchema={validationSchema}
            onSubmit={(values, formik) => {
              mutate(values, {
                onSuccess: () => {
                  setCreateMsg('New Event Created!')
                  setButtonIsClicked(buttonisClicked +1) //updates Recoil global state, to trigger data-table refetch of data
                  formik.resetForm();
                },
                onError: (response) => {
                  setCreateMsg('Error: Event not created - Keep Calm and Call Jonathan');
                  console.log(response);
                },
              });
            }}
          >
            <Form className="formContainer">
              <h1 className="pb-3 text-xl font-semibold">General Information</h1>
              <div className="pb-2 relative">
                <Field
                  className={formikField}
                  autoComplete="off"
                  id="inputCreateEvent"
                  name="event_name"
                  placeholder=" " />
                <label className={formikLabel}>Event Name</label>
                <ErrorMessage
                  name="event_name"
                  component="span"
                  className="text-red-600" />
              </div>
        
        
               <div className="pb-2 relative">
                <Field
                className={formikField}
                as="select"
                name="staff_id"
                id="inputCreateEvent"
                >
                {staff.map(staff => {
                  return(
                    <option key={staff.staff_id} value={staff.staff_id}>{staff.full_name}</option>
                  )
                })}
                </Field>
                <label className={formikLabel}>Drop Down</label>
              </div>
    
    
              <div className="flex flex-col items-center">
                <Button variant="contained" size="large"
                  /* className="text-base text-white bg-blue-500 border hover:bg-blue-600 hover:text-gray-100  p-2 px-20 rounded-lg mt-5" */
                  type="submit"
                >
                  Create Event
                </Button>
              </div>
              <br></br>
            <h1 className= {(createMsg  ==="")  ?  "" : 
            ((createMsg  ==="New Event Created!") ? "text-xl text-blue-600 font-bold p-2 border border-blue-600 text-center":"text-xl text-red-600 font-bold p-2 border border-red-600 text-center")}> {/* This code only formats the class, hence shows the border, when a message is being displayed  */}
                {createMsg}
              </h1>
            </Form>
          </Formik>
        </div>
      );
    }
    
    export default EventsCreate;


Solution

  • Per Randy's comment, the solution was to simply add another option above the {staff.map} array deconstruction. So as follows:

               <div className="pb-2 relative">
                <Field
                className={formikField}
                as="select"
                name="staff_id"
                id="inputCreateEvent"
                >
                  <option>Select from the options...</option>
                {staff.map(staff => {
                  return(
                    <option key={staff.staff_id} value={staff.staff_id}>{staff.full_name}</option>
                  )
                })}
                </Field>
                <label className={formikLabel}>Drop Down</label>
              </div>