1

I created a Formik form that uses Fluent UI v9 components on a Formik form (Input, Textarea and Dropdown). The first 2 work as expected but I cannot figure out how to wire up the Dropdown, resulting in two issues: (a) the dropdown does not update to show the current selection and (b) the state variable for the Dropdown is never updated.

Here is my code (simplified to show Dropdown only). The issue seems to be related to the fact that the severity field in initialValues does not get mapped to the Dropdown component, but I'm guessing.

import React from 'react';
import {Formik, Form} from 'formik';
import {FluentProvider, Label, Dropdown, teamsLightTheme } from '@fluentui/react-components';

// these arrays should be imported from tables
const options=[
  {value:0, label:"Not sure", disabled:false},
  {value:1, label:"We have been hit by a malware attack", disabled:false },
  {value:2, label:"Some or all people cannot work", disabled:false },
  {value:3, label:"A critical application cannot be used" , disabled:false},
  {value:4, label:"Work is slowed", disabled:false },
  {value:5, label:"Work is not disrupted - we have a technical question", disabled:false },
];

export async function saveItem(t){
    // just show the results for now
  console.log(JSON.stringify(t, null, 2));
};


// DropdownBox wraps the fluent-ui Dropdown component with a label and error message
export function DropdownBox({ label, options, ...props }){
    const [field, meta] = useField(props);
    const [selectedOptions,setSelectedOptions] = React.useState([options[field.value].label]);
    const [value,setValue] = React.useState(field.value);
    
    function onOptionSelect(ev, item){
      setSelectedOptions(item.selectedOptions);
      setValue(item.optionItem);
    };
  
    return (
      <div>
        <Label htmlFor={props.name}>{label}</Label>
        <Dropdown {...field} {...props} defaultValue={options[field.value].label} selectedOptions={selectedOptions} onOptionSelect={onOptionSelect} >
          {options.map((option) => (
              <Option key={option.value} value={option.value} disabled={option.disabled}>
                {option.label}
              </Option>
            ))}
        </Dropdown>
        {meta.touched && meta.error ? (
          <div className="error">{meta.error}</div>
        ) : null}
      </div>
    );
  };

export default function MyForm(){
    let item={
        impact: 1,  // the key value of the item selected
    }

  return (
    <FluentProvider theme={teamsLightTheme}>
      <Formik
       initialValues={item}

       onSubmit={async (values,{setSubmitting}) =>{
           setTimeout(async ()=>{
             await saveItem(values);
             setSubmitting(false);
           },(400));}}
      >
        <Form >
          <DropdownBox
              id="impact"
              name="impact"
              label="Business Impact" 
              options={options} 
          />

        </Form>
     </Formik>
    </FluentProvider>
  );
};
Donald Koscheka
  • 336
  • 1
  • 6

1 Answers1

0

I was able to figure this out with some help from this [github repo][1] (Thank you for your help on this).

The onChange method returned from mapFieldToSelect sets the value of the current item based on that item's name. Other changes I made:

  • Switched from Dropdown to Select component in fluentUI because its default "as" is select which Formik recognizes.
  • Added a value field for each option.
  • Changed the variable name to severity to match the control it maps to.

Here is the updated code:

import React from 'react';
import {Formik, Form} from 'formik';
import {FluentProvider, Label, Dropdown, teamsLightTheme } from '@fluentui/react-components';

// these arrays should be imported from tables
const options=[
  {value:0, label:"Not sure", disabled:false},
  {value:1, label:"We have been hit by a malware attack", disabled:false },
  {value:2, label:"Some or all people cannot work", disabled:false },
  {value:3, label:"A critical application cannot be used" , disabled:false},
  {value:4, label:"Work is slowed", disabled:false },
  {value:5, label:"Work is not disrupted - we have a technical question", disabled:false },
];

// utilities would normally go in a separate file
export async function saveItem(t){
    // just show the results for now
  console.log(JSON.stringify(t, null, 2));
};

export function getErrorMessage({ field, form }){
  const error = getIn(form.errors, field.name)
  const touched = getIn(form.touched, field.name)

  return touched ? error : undefined
}

export function invokeAll(...callbacks) {
return () => {
  for (const callback of callbacks) {
      if (callback && typeof callback === 'function') {
          callback()
      }
  }
}
}

export function createFakeEvent({ name }) {
return { target: { name } }
}

export function mapFieldToSelect({field,form}){
  const shared={
    errorMessage:getErrorMessage({field,form}),
    onDismiss:()=>field.onBlur(createFakeEvent(field)),
  }
  return {
    ...shared,
    selectedKey: field.value,
    onChange: (ev,data) => {
      form.setFieldValue(field.name, data.value)
    },
  }
}

// Create a custom control based on the Formik Field Component
export function Formikselect({ field, form, ...props} ){
  const c=useStyles();
  const options=props.options;
  const {errorMessage,  onDismiss, ...fieldProps} = mapFieldToSelect({field, form});

  return (
    <div className={c.formField}>
      <Label htmlFor={field.name} className={c.formLabel}>{props.label}</Label>
      <Select 
        name={field.name}
        errorMessage={errorMessage}
        {...props}
        onDismiss={invokeAll(onDismiss)}
       {...fieldProps}
      >
        {options.map((option) => (
              <option key={option.key} value={option.key} selected={option.key===field.value} disabled={option.disabled}>
                {option.text}
              </option>
        ))}
      </Select>
    </div>
  );
};

export default function MyForm(){
    let item={
        severity: 1,  // the key value of the item selected
    }

  return (
    <FluentProvider theme={teamsLightTheme}>
      <Formik
       initialValues={item}

       onSubmit={async (values,{setSubmitting}) =>{
           setTimeout(async ()=>{
             await saveItem(values);
             setSubmitting(false);
           },(400));}}
      >
        <Form >
          <Field
              name='severity'
              component={Formikselect}
              options={severity} 
              label='Business Impact' 
            />
        </Form>
     </Formik>
    </FluentProvider>
  );
};



  [1]: https://github.com/joaojmendes/formik-fluent-ui-react.git
Donald Koscheka
  • 336
  • 1
  • 6