Created
September 14, 2021 11:59
-
-
Save JustAyush/b106ea77ea13d75a10453d94f93383ce to your computer and use it in GitHub Desktop.
React Select usage with react-hook-form
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { | |
Button, | |
FormControl, | |
FormErrorMessage, | |
FormLabel, | |
Link, | |
SimpleGrid, | |
Stack, | |
} from '@chakra-ui/react'; | |
import { yupResolver } from '@hookform/resolvers/yup'; | |
import React, { ReactElement, useEffect, useState } from 'react'; | |
import { Controller, useForm } from 'react-hook-form'; | |
import ReactSelect from 'react-select'; | |
import * as yup from 'yup'; | |
import SubmitDataDisplay from '../../components/submit-data-display'; | |
import repos from '../../constants/repos'; | |
const avengerOptions = [ | |
{ value: 'Iron Man', label: 'Iron Man' }, | |
{ value: 'Captain America', label: 'Captain America' }, | |
{ value: 'Spiderman', label: 'Spiderman' }, | |
{ value: 'Thor', label: 'Thor' }, | |
{ value: 'The Hulk', label: 'The Hulk' }, | |
{ value: 'Captain Marvel', label: 'Captain Marvel' }, | |
]; | |
type ReactSelectOption = { | |
value: string | number; | |
label: string; | |
unit?: string; | |
}; | |
type FormData = { | |
favouriteAvenger: ReactSelectOption | null; | |
favouriteAvengerPersisted: ReactSelectOption | null; | |
}; | |
const validationSchema = yup.object({ | |
// Instead of having required on individual properties of shape schema, if you are sure all the properties | |
// are mandatory, the required function can be called on the object shape directly. | |
favouriteAvenger: yup | |
.object() | |
.shape({ | |
label: yup.string(), | |
value: yup.string(), | |
}) | |
.nullable() | |
.required('Favourite Avenger is required'), | |
favouriteAvengerPersisted: yup | |
.object() | |
.shape({ | |
label: yup.string(), | |
value: yup.string(), | |
}) | |
.nullable() | |
.required('Favourite Avenger is required'), | |
}); | |
const defaultValues = { | |
favouriteAvenger: null, | |
favouriteAvengerPersisted: null, | |
}; | |
export default function (): ReactElement { | |
const { | |
handleSubmit, | |
formState: { errors }, | |
control, | |
reset, | |
setValue, | |
} = useForm<FormData>({ | |
resolver: yupResolver(validationSchema), | |
defaultValues: defaultValues, | |
}); | |
// Draft to the formData stored in localstorage | |
const [draft, setDraft] = useState<{ | |
favouriteAvengerPersisted: ReactSelectOption | null; | |
} | null>(null); | |
// It is recommend to extract required properties (value in this case) | |
// from react-select in onSubmitHandler | |
const onSubmit = (data: FormData) => { | |
const dataToSubmit = { | |
favouriteAvenger: data.favouriteAvenger?.value, | |
favouriteAvengerPersisted: data.favouriteAvengerPersisted?.value, | |
}; | |
alert(JSON.stringify(dataToSubmit)); | |
}; | |
const onReset = () => { | |
reset(defaultValues); | |
setDraft(null); | |
localStorage.removeItem('favouriteAvengerPersisted'); | |
}; | |
const handleFieldChange = ( | |
fieldName: 'favouriteAvengerPersisted', | |
value: ReactSelectOption | null | |
) => { | |
setDraft({ | |
[fieldName]: value, | |
}); | |
localStorage.setItem(fieldName, JSON.stringify(value)); | |
}; | |
useEffect(() => { | |
const value = localStorage.getItem('favouriteAvengerPersisted'); | |
if (!value) return; | |
const parsedValue = JSON.parse(value); | |
setDraft({ favouriteAvengerPersisted: parsedValue }); | |
setValue('favouriteAvengerPersisted', parsedValue); | |
}, [setValue]); | |
return ( | |
<Stack spacing="10"> | |
<SimpleGrid gap="10" columns={2}> | |
<form onSubmit={handleSubmit(onSubmit)}> | |
<Stack spacing="6"> | |
<FormControl isInvalid={!!errors?.favouriteAvenger}> | |
<FormLabel>Favourite Avenger</FormLabel> | |
<Controller | |
control={control} | |
name="favouriteAvenger" | |
render={({ field }) => ( | |
<ReactSelect | |
id="favouriteAvenger" | |
{...field} | |
placeholder="Choose option" | |
options={avengerOptions} | |
/> | |
)} | |
/> | |
<FormErrorMessage> | |
{errors?.favouriteAvenger && errors?.favouriteAvenger?.message} | |
</FormErrorMessage> | |
</FormControl> | |
<FormControl isInvalid={!!errors?.favouriteAvenger}> | |
<FormLabel> | |
Favourite Avenger (Persisted with Local Storage) | |
</FormLabel> | |
<Controller | |
control={control} | |
name="favouriteAvengerPersisted" | |
render={({ field: { onChange, onBlur, ref } }) => ( | |
<ReactSelect | |
id="favouriteAvengerPersisted" | |
onBlur={onBlur} | |
onChange={(selectedOption) => { | |
onChange(selectedOption); | |
handleFieldChange( | |
'favouriteAvengerPersisted', | |
selectedOption | |
); | |
}} | |
value={draft?.favouriteAvengerPersisted || null} | |
inputRef={ref} | |
placeholder="Select option" | |
options={avengerOptions} | |
/> | |
)} | |
/> | |
<FormErrorMessage> | |
{errors?.favouriteAvengerPersisted && | |
errors?.favouriteAvengerPersisted?.message} | |
</FormErrorMessage> | |
</FormControl> | |
<Button colorScheme="primary" type="submit"> | |
Submit | |
</Button> | |
<Button colorScheme="gray" onClick={onReset}> | |
Reset | |
</Button> | |
</Stack> | |
</form> | |
<SubmitDataDisplay control={control} /> | |
</SimpleGrid> | |
<Link | |
href={repos.chakraUIFormFields.input} | |
isExternal | |
color="primary.main" | |
_focus={{ outline: 'none' }}> | |
View Source | |
</Link> | |
</Stack> | |
); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
hello. ReactSelect does not have inputRef prop. When I used as Custom component , I wrapp forwardRef and now I have ref type error. Can you help me show ref's type?