Home > front end >  How to change Form initialValues and Fields based on selection in ReactJS?
How to change Form initialValues and Fields based on selection in ReactJS?

Time:04-07

I have an object like this :

const initialValues = {
    "Name": "",
    "Price": "",
    "Quantity": "",
    "Details": {
        "Light": "",
        "Watering": "",
        "Maintenance": "",
        "WhereToGrow": "",
    },
    "BrowseImg": "",
    "MainImg": "",
    "Tags": "",
    "Category": "Plant",
    "SubCategory": ""
}

I am using React Formik library in my Form Component, I am rendering selection& Details object like this:


  <Grid item xs={6}>
    <FormControl required>
      <InputLabel id="demo-simple-select-required-label">Category</InputLabel>
      <Select
        labelId="demo-simple-select-required-label"
        id="demo-simple-select-required"
        value={values.Category !== "undefined" ? values.Category : ""}
        label="Category *"
        onChange={(e) => {
          setFieldValue("Category", e.target.value);
        }}
        autoWidth
        name="Category"
        defaultValue=""
      >
        <MenuItem value="">
          <em>None</em>
        </MenuItem>
        <MenuItem value={"Plant"}>Plant</MenuItem>
        <MenuItem value={"Planters"}>Planters</MenuItem>
        <MenuItem value={"Seeds"}>Seeds</MenuItem>
      </Select>
    </FormControl>
  </Grid>
  <FieldArray name="Details">
    <Grid container spacing={2} sx={{ padding: "20px" }}>
      <Grid item xs={12}>
        <Typography>Details</Typography>
      </Grid>

      <Grid item xs={6}>
        <TextFieldWrapper
          name={
            values.Category !== "Planters"
              ? `Details.Light`
              : `Details.Material`
          }
          label={values.Category !== "Planters" ? `Light` : "Material"}
        />
      </Grid>
      <Grid item xs={6}>
        <TextFieldWrapper
          name={
            values.Category !== "Planters"
              ? `Details.Watering`
              : `Details.Build`
          }
          label={values.Category !== "Planters" ? `Watering` : "Build"}
        />
      </Grid>
      <Grid item xs={6}>
        <TextFieldWrapper
          name={
            values.Category !== "Planters"
              ? `Details.Maintenance`
              : `Details.PlanterHeight`
          }
          label={
            values.Category !== "Planters" ? `Maintenance` : "Planter Height"
          }
        />
      </Grid>
      <Grid item xs={6}>
        <TextFieldWrapper
          name={
            values.Category !== "Planters"
              ? `Details.WhereToGrow`
              : `Details.PlanterWidth`
          }
          label={
            values.Category !== "Planters" ? `Where To Grow` : "Planter Width"
          }
        />
      </Grid>
    </Grid>
  </FieldArray>

Now, I need to change initialValues object when "Planters" is selected to :

{
    "Name": "",
    "Price": "",
    "Quantity": "",
    "Details": {
        "Material": "",
        "Build": "",
        "PlanterHeight": "",
        "PlanterWidth": ""
    },
    "BrowseImg": "",
    "MainImg": "",
    "Tags": "",
    "Category": "Planters",
    "SubCategory": ""
}

I tried this to change key Name in initialValues object like this:

    const Context = () => {
      const { values } = useFormikContext();

      useEffect(() => {
        if (values.Category === "Planters") {
          delete Object.assign(initialValues.Details, {
            Material: initialValues.Details.Light,
          })["Light"];
          delete Object.assign(initialValues.Details, {
            Build: initialValues.Details.Watering,
          })["Watering"];
          delete Object.assign(initialValues.Details, {
            PlanterHeight: initialValues.Details.Maintenance,
          })["Maintenance"];
          delete Object.assign(initialValues.Details, {
            PlanterWidth: initialValues.Details.WhereToGrow,
          })["WhereToGrow"];

          console.log("VALUES FORMIK CONTEXT", values);
          console.log("INITIALVALUES", initialValues);
          console.log(
            "INITIAL VALUES DETAILS ",
            initialValues.Details.Material
          );
        }
      }, [values]);
      return null;
    };

changing name when values.Category is Planters throws Formik Error as follows:

Warning: A component is changing a controlled input to be uncontrolled. This is likely caused by the value changing from a defined to undefined, which should not happen. Decide between using a controlled or uncontrolled input element for the lifetime of the component.

How do I achieve this? Thanks in advance!

CodePudding user response:

This is not quite correct approach, and it's not good idea for me to change set of fields in a form on-the-fly - you'll have problems supporting your own tangled code, this useEffect is pure mess.

Keep in mind that form data you use for UI may differ from data you send to backend and can be transformed right before you make a request. Actually initialValues is for initializing your form and should not be changed from inside the form. I'd create universal set of field.

For example, I'd change data initial data like this

// read-only array to render Select, add as much item as you like.
// In general, since it's just an array for options,
// it can be safely extracted to external constant
// and may not present in initialData. 
Categories: [
 {
  name 'Planers',
  selected: true,
 },
 {
   name: 'Plants'
 } 
],
SelectedCategory: 'Planers', // since selected is true 
Planters: {
  "Material": "",
  "Build": "",
  "PlanterHeight": "",
  "PlanterWidth": ""
},
Plants: {
  "Light": "",
  "Watering": "",
  "Maintenance": "",
  "WhereToGrow": "",
}

And then in your form

// ...
const { values, handleChange, handleBlur } = useFormikContext();

<Select
  onChange={handleChange} // or your own handler
  onBlur={handleBlur}
  name="SelectedCategory"
>
{values.Categories.map(({name, selected}) => (
  <MenuItem
   key={name}
   selected={selected}
  >
   {name}
  </MenuItem >
))}
</Select>

and in the same component two or more sets of fields. Each set is rendered according to SelectedCategory value. There's no need to change or reset values in sets of fields when SelectedCategory changes - you can remove or reset the fields on the stage of sending the data.

if (values.SelectedCategory === 'Planers') {
  // render set of fields for Planers
}
if (values.SelectedCategory === 'Plants') {
  // render set of fields for Plants
}

And onSubmit you can transform your filed values as you like:

<Formik
  initialValues
  onSubmit={(values, {resetForm}) => {
      const transformedDataForAsyncRequest = yourCustomDataTransducer(values);
      axios({
        method: 'post',
        data: transformedDataForAsyncRequest
      })
        .then(() => {resetForm()})
        .catch((e) => {console.log(e)})
    }
  }
  • Related