Using Controller component from react-hook-form

Hi there, I’m wondering what is the correct way to get a controller component working with redwood forms.

I feel like I have everything setup right but I can not get the controller to register and I’m not seeing any data.

Here is my code

import { useForm, Controller } from 'react-hook-form'
import Select from 'react-select/creatable'

export const QUERY = gql`
  query FindPlants {
    plants {
      id
      title
    }
  }
`

export const Loading = () => <div>Loading...</div>

export const Empty = () => <div>Empty</div>

export const Failure = ({ error }) => (
  <div style={{ color: 'red' }}>Error: {error.message}</div>
)

export const Success = ({ plants }) => {
  const methods = useForm()

  return (
    <Controller
      control={methods.control}
      defaultValue={[]}
      name="field_name_product"
      render={({ field }) => {
        console.log(field)
        const { onChange, value, name, ref } = field
        return (
          <Select
            name={name}
            inputRef={ref}
            options={plants}
            value={plants.find((c) => c.id === value)}
            onChange={(val) => {
              onChange(val)
            }}
            getOptionValue={(option) => option.id}
            getOptionLabel={(option) => option.title}
            className="basic-multi-select"
            classNamePrefix="select"
            isMulti
          />
        )
      }}
    />
  )
}

I’m importing this into a standard redwood form, but the data is never submitted.

I have also tried putting the controller component directly in the form and that did not work either

Do i need to register or something?

Thanks
Shannon

Hi Shannon! Welcome to the Redwood community!

If I understand your problem correctly, when you submit with form the Controller there’s no data. Is that right?

Only thing I’d change is to wrap with <form>. Something like this:

<form onSubmit={methods.handleSubmit((data) => console.log(data))}>
  // <Your Controller magic here>
  <button type="submit">Submit</button>
</form>

Here’s a short tape to show you what I’m seeing: https://s.tape.sh/R7uB0cmD
Here’s the full code of the cell in this tape: https://gist.github.com/callingmedic911/07ffe53217a09339c842f631c9e084ac

Here’s an example from react-hook-forms using react-select: https://react-hook-form.com/get-started/#integratingwithuilibraries

Let me know if missed anything. I’d be glad to help. :slight_smile:

Thanks for the quick reply. The issue I am having is more around using the controller in an existing redwood form.

Here is a quick loom video to show the problem Loom | Free Screen & Video Recording Software

And some more complete code here:

import {
  Form,
  FormError,
  FieldError,
  Label,
  TextField,
  Submit,
} from '@redwoodjs/forms'

import { useForm, Controller } from 'react-hook-form'
import Select from 'react-select/creatable'

const NoteForm = (props) => {
  const options = [
    { id: 1, title: 'plant 1' },
    { id: 2, title: 'plant 2' },
  ]

  const methods = useForm()

  const onSubmit = (data) => {
    console.log(data)
    props.onSave(data, props?.note?.id)
  }

  return (
    <div className="rw-form-wrapper">
      <Form
        onSubmit={onSubmit}
        // onSubmit={methods.handleSubmit((data) =>
        //     console.log('data-----', data)
        //   )}
        error={props.error}
      >
        <FormError
          error={props.error}
          wrapperClassName="rw-form-error-wrapper"
          titleClassName="rw-form-error-title"
          listClassName="rw-form-error-list"
        />

        <Label
          name="description"
          className="rw-label"
          errorClassName="rw-label rw-label-error"
        >
          Description
        </Label>
        <TextField
          name="description"
          defaultValue={props.note?.description}
          className="rw-input"
          errorClassName="rw-input rw-input-error"
          validation={{ required: true }}
        />

        <FieldError name="description" className="rw-field-error" />

        {/* <PlantSelectCell /> */}

        <Controller
          control={methods.control}
          defaultValue={[]}
          name="field_name_product"
          render={({ field }) => {
            console.log(field)
            const { onChange, value, name, ref } = field
            return (
              <Select
                name={name}
                inputRef={ref}
                options={options}
                value={options.find((c) => c.id === value)}
                onChange={(val) => {
                  onChange(val)
                }}
                getOptionValue={(option) => option.id}
                getOptionLabel={(option) => option.title}
                className="basic-multi-select"
                classNamePrefix="select"
                isMulti
              />
            )
          }}
        />

        <div className="rw-button-group">
          <Submit disabled={props.loading} className="rw-button rw-button-blue">
            Save
          </Submit>
        </div>
      </Form>
    </div>
  )
}

export default NoteForm

Got it! Thanks for the detailed explanation.

I think the Form and the Controller within are referring to the different useForm() instance. Since Redwood form uses FormProvider, you can get rid of useForm in the cell and remove the prop control from the Controller.

Let me know if this resolves your issue.

Perfect Thanks so much @callingmedic911

1 Like

@shansmith01 @callingmedic911
Hi,

I have a very similar question, I was looking for exactly this and solution with NOT using separate controller helped.

I have slightly different problem:

  1. When I try to submit data the ‘plants’ from here are sent as array of objects instead of array of ID’s (as I think it should be sent)
  2. If I change the onChange method to return ID’s the react-select breaks with undefined values (as title and id fields do not exist
  3. In either case the GraphQL schema for my update mutation is not expecting that type to be present on the update input and throws an error.

So here’s a question:

A. what can I do to make GraphQL aware that when I update main object (say Garden) I want to assign multiple Plants to it? Since the schema is generated I should pick up changes I made to EditGardenCell or am I missing something?

B. Assuming I will need to send only IDs to the GraphQL endpoint (like with connect) how can I do this without breaking react-select?

input UpdateGardenInput {
  description: String
  title: String
}
import Select from 'react-select';

import { Controller } from '@redwoodjs/forms';

// use this as data for the Select field
// const options = [
//   { id: 'chocolate', title: 'Chocolate' },
//   { id: 'strawberry', title: 'Strawberry' },
//   { id: 'vanilla', title: 'Vanilla' },
// ];

const SelectMultipleChoice = ({
  name = 'plants',
  options = [],
  defaultValues,
  control,
}) => {
  return (
    <Controller
      name={name}
      render={({ field }) => {
        return (
          <Select
            {...field}
            options={options}
            defaultValue={defaultValues}
            getOptionValue={(option) => option?.id}
            getOptionLabel={(option) => option?.title}
            isMulti
            onChange={(val) => {
              field.onChange(val);
            }}
            // this will return the IDs to GraphQL (but it will break react-select) with name of form input - here `plants`
            // onChange={(val) => {
            //   return field.onChange(
            //     val.map((element) => {
            //       return element.id;
            //     })
            //   );
            // }}
          />
        );
      }}
    />
  );
};

export default SelectMultipleChoice;