values object is not updated while using react-select inside field array

Viewed 47

Since I want my variation form to be dynamic, I have used the concept of field array from react-hook-form. For the select field I am using react-select which is used for taking multiple values for a particular option from the user. This is how I have done

import React from "react";
import { useForm, useFieldArray } from "react-hook-form";

import ReactSelect from "./Select";
import Input from "./Input";
import VariationPreview from "./VariationPreview";

const initialValues = {
  variations: [
    {
      id: 1,
      option: "Size",
      values: [
        { id: 1, label: "SM", value: "sm" },
        { id: 2, label: "MD", value: "md" }
      ]
    },
    {
      id: 2,
      option: "Color",
      values: [
        { id: 1, label: "Red", value: "red" },
        { id: 2, label: "Blue", value: "blue" }
      ]
    }
  ]
};

const VariationSetup = () => {
  const {
    register,
    control,
    handleSubmit,
    formState: { errors },
    watch,
    getValues
  } = useForm({
    defaultValues: initialValues ?? {}
  });
  const { fields, append, remove } = useFieldArray({
    control, // control props comes from useForm (optional: if you are using FormContext)
    name: "variations" // unique name for your Field Array
  });
  const variants = watch("variations");
  console.log("variants", variants);
  return (
    <>
      <p className="font-semibold mb-4 text-md">Options</p>
      {fields.map((field, index) => {
        return (
          <React.Fragment key={field.id}>
            <div className="flex mb-4" key={field.id}>
              <div className="w-full md:w-4/12">
                <Input
                  name={`variations.${index}.option`}
                  label="Option"
                  placeholder="Choose option"
                  options={VARIATION_OPTION}
                  helperText="Choose option that can be applied as variants for a product"
                  errors={errors}
                  register={register}
                />
              </div>
              <div className="w-full md:ml-4 md:w-8/12">
                <ReactSelect
                  name={`variations.${index}.values`}
                  label=""
                  placeholder="Choose value"
                  options={[]}
                  helperText="You can choose multiple values"
                  wrapperClassName="mt-7"
                  errors={errors}
                  register={register}
                  isCreateable
                />
              </div>
            </div>
          </React.Fragment>
        );
      })}
      <button
        className="bg-gray-200 p-3"
        type="button"
        onClick={() => append({ option: "", value: "" })}
      >
        Add option
      </button>
      <div className="divide-y"></div>
      {/* PREVIEW */}
      <VariationPreview variations={watch("variations")} />
    </>
  );
};

export default VariationSetup;

const VARIATION_OPTION = [
  { id: 1, label: "Size", value: "size" },
  { id: 2, label: "Color", value: "color" }
];

this is the react-select with hook-form binding

import { Controller, useForm } from "react-hook-form";
import Select from "react-select";
import CreatableSelect from "react-select/creatable";

export default function ReactSelect({
  disabled,
  label,
  helperText,
  name,
  placeholder,
  options,
  defaultValue,
  className,
  labelClassName,
  wrapperClassName,
  isCreateable = false
}) {

  const {
    control,
    formState: { errors }
  } = useForm();

  const getDefaultValue = (value) => {
    if (value && value.length) {
      return value[0];
    } else return value;
  };

  return (
    <div className={wrapperClassName ? wrapperClassName : ""}>
      {/* TODO: Label as a separate component */}
      <label
        htmlFor={name}
        className={
          labelClassName
            ? labelClassName
            : "block font-semibold mb-2 text-gray-700 text-sm tracking-wide uppercase"
        }
      >
        {label}
      </label>
      <div className="mt-1 relative">
        <Controller
          name={name}
          defaultValue={getDefaultValue(defaultValue)}
          control={control}
          render={({ field }) => {
            const styles = errors[name] ? errorStyles : customStyles;
            if (isCreateable) {
              return (
                <CreatableSelect
                  isMulti
                  {...field}
                  isDisabled={disabled}
                  placeholder={placeholder}
                  options={options}
                  styles={styles}
                  className={className}
                />
              );
            } else {
              return (
                <Select
                  {...field}
                  isDisabled={disabled}
                  placeholder={placeholder}
                  options={options}
                  styles={styles}
                  className={className}
                />
              );
            }
          }}
        />
      </div>
    </div>
  );
}

Here the problem is when I update values field the variations object does not get updated because of which I cannot update Variation Preview table. Also when if I add new option, the option key gets updated and is reflected in VariationPreview table but when I add values for that option the values object is shown empty. Could anyone point me where i did the mistake? I have a code in playground either

https://codesandbox.io/s/elegant-robinson-fiujlj?file=/src/Variation.jsx:0-2764

1 Answers

You call useForm inside the ReactSelect component. Now there are two unrelated forms. That's why variations object does not get updated.

You can pass the control down to ReactSelect and use it for Controller component.

And for errors handling:

const { errors } = useFormState({
  control
});
Related