Search code examples
reactjsreact-functional-componentreact-hook-form

Controller submitting undefined with React Select


I'm trying to use React Select with useForm and got stuck when submitting form values.

My goal is a multi value react select with a default value and onChange function to do some validations when changing (like limit the number of itens to 3).

I already tried to search the solution in others posts and did make some changes in my code but unfortunately I did not succeed.

Everything seems to work perfectly but when I submit the form, my controller results in undefined value.

import React, {useEffect, useState, useContext} from 'react'
import {useForm, Controller} from 'react-hook-form'
import axios from 'axios';
import Select from 'react-select';
import BeeUtils from '../../../utils/BeeUtils'

export default function EditCategory2({place, goBack}){
   var messageFieldRequired = 'Campo Obrigatório';
    
      const audienceOptions = [
        { value: 'Lésbicas', label: 'Lésbicas' },
        { value: 'Gays', label: 'Gays' },
        { value: 'Bissexuais', label: 'Bissexuais' },
        { value: 'Transexuais', label: 'Transexuais' },
        { value: 'Queer', label: 'Queer' },
        { value: 'Intersexo', label: 'Intersexo' },
        { value: 'Assexual', label: 'Assexual' },
        { value: 'Héteros', label: 'Héteros' },
        { value: 'Todxs', label: 'Todxs' }
      ]

    const handleAudienceSelector = (e) => {
        console.log('OK');    
        console.log(e);
        if(e.length > 3){
                e.pop();
                alert('max of 3 selected');
        }
    }
    
    
    const {register , handleSubmit, errors, setValue, getValues, setError, control} = useForm();

    
    const requestUpdate = async (data) => {
        data.createdBy = place.createdBy;
        data._id = place._id;
        data.recordUpdatedType = 'audience';
        console.log(data);
        return;
    }


    const selectRequired = (e) => {
        console.log(e);
        console.log('OK-2');
        //var error = e.length == 0? messageFieldRequired : '';
        //return error;
    }
    
    const onSubmit = data => {
            console.log(data)
            requestUpdate(data);
        }  
    
        return (
            <div className='cad-form'>
          <form onSubmit={handleSubmit(onSubmit)}> 
          <div className='cad-tit-container'> 
             <span className='cad-titulo'> Edit Here</span>
          </div>
          
          <div className='cad-container'>

            <label htmlFor='test-audience'>Audience</label>  
            <Controller
                    name="test-audience"
                    control={control}
                    rules={{ validate: selectRequired }}
                    render={() => (
                        <Select 
                            defaultValue={[audienceOptions[0], audienceOptions[1]]}
                            isMulti
                            onChange={handleAudienceSelector}
                            placeholder='Select Itens'
                            options={audienceOptions}
                            className="basic-multi-select selectCustom"
                            classNamePrefix="select"
                        />
                        )}
                />
            {errors?.targetAudience && <p>{errors.targetAudience.message}</p>}

          </div>

          <div className='btn-container'>
          <div className='cad-btn'><button onClick={(e) => goBack('initial')} className="btn waves-effect yellow darken-2">Voltar</button></div>
          <div className='cad-btn'><button type='submit' className="btn waves-effect yellow darken-2">Salvar Alterações</button></div>
          </div>
        </form>
        </div>
        )
    }

After some changes (thanks to help of the answer) I tried this code

import React, {useEffect, useState, useContext} from 'react'
import {useForm, Controller} from 'react-hook-form'
import axios from 'axios';
import Select from 'react-select';
import BeeUtils from '../../../utils/BeeUtils'

export default function EditCategory2({place, goBack}){
    var messageFieldRequired = 'Campo Obrigatório';
    
      const audienceOptions = [
        { value: 'Lésbicas', label: 'Lésbicas' },
        { value: 'Gays', label: 'Gays' },
        { value: 'Bissexuais', label: 'Bissexuais' },
        { value: 'Transexuais', label: 'Transexuais' },
        { value: 'Queer', label: 'Queer' },
        { value: 'Intersexo', label: 'Intersexo' },
        { value: 'Assexual', label: 'Assexual' },
        { value: 'Héteros', label: 'Héteros' },
        { value: 'Todxs', label: 'Todxs' }
      ]

    const handleAudienceSelector = (e) => {
        console.log('OK');    
        console.log(e);
        if(e.length > 3){
                e.pop();
                alert('max of 3 selected');
        }
    }
    
    
    const {register , handleSubmit, errors, setValue, getValues, setError, control} = useForm();

    
    const requestUpdate = async (data) => {
        data.createdBy = place.createdBy;
        data._id = place._id;
        data.recordUpdatedType = 'audience';
        console.log(data);
        return;
    }
    
    const onSubmit = data => {
            console.log(data)
            requestUpdate(data);
        }  
    
        return (
            <div className='cad-form'>
          <form onSubmit={handleSubmit(onSubmit)}> 
          <div className='cad-tit-container'> 
             <span className='cad-titulo'> Edit Here</span>
          </div>
          
          <div className='cad-container'>

            <label htmlFor='test-audience'>Audience</label>  
            <Controller
              name="targetAudience"
              control={control}
              defaultValue={[audienceOptions[0], audienceOptions[1]]}
              rules={{ required: messageFieldRequired }}
              render={({ field: { onChange, value } }) => (
                <Select
                  value={value}
                  onChange={onChange}
                  isMulti
                  placeholder="Select Itens"
                  options={audienceOptions}
                  className="basic-multi-select selectCustom"
                  classNamePrefix="select"
                />
              )}
            />

          </div>

          <div className='btn-container'>
          <div className='cad-btn'><button onClick={(e) => goBack('initial')} className="btn waves-effect yellow darken-2">Voltar</button></div>
          <div className='cad-btn'><button type='submit' className="btn waves-effect yellow darken-2">Salvar Alterações</button></div>
          </div>
        </form>
        </div>
        )
    }

But now I got the error: TypeError: Cannot read property 'onChange' of undefined


Solution

  • The reason why it isn't working is because you forgot to to link the <Controller /> component with the <Select /> component via the value and onChange properties of the render prop function from <Controller />.

    <Controller
      name="targetAudience"
      control={control}
      defaultValue={[audienceOptions[0], audienceOptions[1]]}
      rules={{ required: "Campo obrigatório", validate: isOnly3Values }}
      render={({ field: { onChange, value } }) => (
        <Select
          value={value}
          onChange={onChange}
          isMulti
          placeholder="Select Itens"
          options={audienceOptions}
          className="basic-multi-select selectCustom"
          classNamePrefix="select"
        />
      )}
    />
    

    You also don't need to use useState here for handling the error state as RHF already provides this functionality. For setting a field to be required, you can just set the required property of the validation object which can be passed to <Controller /> via the rules prop. Check here for more information about <Controller />. I would suggest to also use the validate function from RHF to check if the user added more than 3 items to your <Select /> and display an error message instead of using an alert.

    I made some overall small changes and corrected some minor issues (e.g. the errors object is located in the formState property since v7). Feel free to write a comment if something isn't clear.

    Edit React Hook Form - Basic (forked)