react-hook-form#FieldError TypeScript Examples
The following examples show how to use
react-hook-form#FieldError.
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: validation.ts From yasd with MIT License | 7 votes |
export function getValidationHint(
typeMap: {
[key in keyof RegisterOptions]?: string
} & {
[key: string]: any
},
fieldError?: FieldError,
): string | undefined {
if (!fieldError) return undefined
for (const key in typeMap) {
if (fieldError.type === key) {
return typeMap[key]
}
}
return fieldError.message
}
Example #2
Source File: handle-form-error.tsx From admin with MIT License | 7 votes |
function getFormErrors(errors: DeepMap<FieldValues, FieldError>) {
const messages: string[] = Object.values(errors).reduce(
(acc, { message }) => {
if (message) {
acc.push(message)
}
return acc
},
[]
)
const refs = Object.values(errors).reduce((acc, { ref }) => {
if (ref) {
acc.push(ref)
}
return acc
}, [])
const list = (
<ul className="list-disc list-inside">
{messages.map((m) => (
<li>{m}</li>
))}
</ul>
)
const title =
messages.length > 1
? `There were ${messages.length} errors with your submission`
: "There was an error with your submission"
return { title, list, refs }
}
Example #3
Source File: EmailPassword.tsx From firebase-tools-ui with Apache License 2.0 | 6 votes |
function getErrorText(errors: { [key: string]: FieldError | undefined }) {
if (errors.email) {
if (errors.email.type === 'pattern') {
return 'Invalid email';
}
if (errors.email.type === 'unique') {
return 'User with this email already exists';
}
}
if (errors.email?.type === 'both' || errors.password?.type === 'both') {
return 'Both email and password should be present';
}
if (errors.password?.type === 'minLength') {
return `Password should be at least ${PASSWORD_MIN_LENGTH} characters`;
}
}
Example #4
Source File: forms.ts From mui-toolpad with MIT License | 6 votes |
function errorMessage(error: FieldError) {
if (error.message) {
return error.message;
}
switch (error.type) {
case 'required':
return 'required';
default:
return 'invalid';
}
}
Example #5
Source File: forms.ts From mui-toolpad with MIT License | 6 votes |
export function validation<T>(
formState: FormState<T>,
field: keyof T,
): { error?: boolean; helperText?: string } {
const error: FieldError = (formState.errors as any)[field];
return {
error: !!error,
helperText: error ? errorMessage(error) : undefined,
};
}
Example #6
Source File: TextFieldElement.stories.tsx From react-hook-form-mui with MIT License | 5 votes |
parseError = (error: FieldError) => {
if (error.type === 'pattern') {
return 'Enter an email'
}
return 'This field is required'
}
Example #7
Source File: CreateEditChannelView.tsx From atlas with GNU General Public License v3.0 | 4 votes |
CreateEditChannelView: React.FC<CreateEditChannelViewProps> = ({ newChannel }) => {
const avatarDialogRef = useRef<ImageCropModalImperativeHandle>(null)
const coverDialogRef = useRef<ImageCropModalImperativeHandle>(null)
const [avatarHashPromise, setAvatarHashPromise] = useState<Promise<string> | null>(null)
const [coverHashPromise, setCoverHashPromise] = useState<Promise<string> | null>(null)
const { activeMemberId, activeAccountId, activeChannelId, setActiveUser, refetchActiveMembership } = useUser()
const { joystream, proxyCallback } = useJoystream()
const handleTransaction = useTransaction()
const { displaySnackbar } = useSnackbar()
const nodeConnectionStatus = useConnectionStatusStore((state) => state.nodeConnectionStatus)
const addNewChannelIdToUploadsStore = useUploadsStore((state) => state.actions.addNewChannelId)
const navigate = useNavigate()
const { ref: actionBarRef, height: actionBarBoundsHeight = 0 } = useResizeObserver({ box: 'border-box' })
const {
channel,
loading,
error,
refetch: refetchChannel,
} = useChannel(activeChannelId || '', {
skip: newChannel || !activeChannelId,
onError: (error) =>
SentryLogger.error('Failed to fetch channel', 'CreateEditChannelView', error, {
channel: { id: activeChannelId },
}),
})
const startFileUpload = useStartFileUpload()
// trigger use asset to make sure the channel assets get resolved
useAsset(channel?.avatarPhoto)
useAsset(channel?.coverPhoto)
const {
register,
handleSubmit: createSubmitHandler,
control,
formState: { isDirty, dirtyFields, errors, isValid },
watch,
setFocus,
setValue,
reset,
} = useForm<Inputs>({
mode: 'onChange',
defaultValues: {
avatar: { contentId: null, assetDimensions: null, imageCropData: null },
cover: { contentId: null, assetDimensions: null, imageCropData: null },
title: '',
description: '',
language: languages[0].value,
isPublic: true,
},
})
const addAsset = useAssetStore((state) => state.actions.addAsset)
const avatarAsset = useRawAsset(watch('avatar').contentId)
const coverAsset = useRawAsset(watch('cover').contentId)
const { isWorkspaceOpen, setIsWorkspaceOpen } = useVideoWorkspace()
const { fetchOperators } = useOperatorsContext()
useEffect(() => {
if (newChannel) {
reset({
avatar: { contentId: null },
cover: { contentId: null },
title: '',
description: '',
language: languages[0].value,
isPublic: true,
})
}
}, [newChannel, reset])
useEffect(() => {
if (loading || newChannel || !channel) {
return
}
const { title, description, isPublic, language } = channel
const foundLanguage = languages.find(({ value }) => value === language?.iso)
reset({
avatar: {
contentId: channel.avatarPhoto?.id,
assetDimensions: null,
imageCropData: null,
},
cover: {
contentId: channel.coverPhoto?.id,
assetDimensions: null,
imageCropData: null,
},
title: title || '',
description: description || '',
isPublic: isPublic ?? false,
language: foundLanguage?.value || languages[0].value,
})
}, [channel, loading, newChannel, reset])
useEffect(() => {
if (!dirtyFields.avatar || !avatarAsset?.blob) {
return
}
const hashPromise = computeFileHash(avatarAsset.blob)
setAvatarHashPromise(hashPromise)
}, [dirtyFields.avatar, avatarAsset])
useEffect(() => {
if (!dirtyFields.cover || !coverAsset?.blob) {
return
}
const hashPromise = computeFileHash(coverAsset.blob)
setCoverHashPromise(hashPromise)
}, [dirtyFields.cover, coverAsset])
const headTags = useHeadTags(newChannel ? 'New channel' : 'Edit channel')
const handleSubmit = createSubmitHandler(async (data) => {
await submit(data)
})
const handleCoverChange: ImageCropModalProps['onConfirm'] = (
croppedBlob,
croppedUrl,
assetDimensions,
imageCropData
) => {
const newCoverAssetId = `local-cover-${createId()}`
addAsset(newCoverAssetId, { url: croppedUrl, blob: croppedBlob })
setValue('cover', { contentId: newCoverAssetId, assetDimensions, imageCropData }, { shouldDirty: true })
}
const handleAvatarChange: ImageCropModalProps['onConfirm'] = (
croppedBlob,
croppedUrl,
assetDimensions,
imageCropData
) => {
const newAvatarAssetId = `local-avatar-${createId()}`
addAsset(newAvatarAssetId, { url: croppedUrl, blob: croppedBlob })
setValue('avatar', { contentId: newAvatarAssetId, assetDimensions, imageCropData }, { shouldDirty: true })
}
const submit = async (data: Inputs) => {
if (!joystream || !activeMemberId || !activeAccountId) {
return
}
setIsWorkspaceOpen(false)
const metadata: ChannelInputMetadata = {
...(dirtyFields.title ? { title: data.title?.trim() ?? '' } : {}),
...(dirtyFields.description ? { description: data.description?.trim() ?? '' } : {}),
...(dirtyFields.language || newChannel ? { language: data.language } : {}),
...(dirtyFields.isPublic || newChannel ? { isPublic: data.isPublic } : {}),
ownerAccount: activeAccountId,
}
const assets: ChannelInputAssets = {}
const processAssets = async () => {
if (dirtyFields.avatar && avatarAsset?.blob && avatarHashPromise) {
const ipfsHash = await avatarHashPromise
assets.avatarPhoto = {
size: avatarAsset.blob.size,
ipfsHash,
replacedDataObjectId: channel?.avatarPhoto?.id,
}
}
if (dirtyFields.cover && coverAsset?.blob && coverHashPromise) {
const ipfsHash = await coverHashPromise
assets.coverPhoto = {
size: coverAsset.blob.size,
ipfsHash,
replacedDataObjectId: channel?.coverPhoto?.id,
}
}
}
const uploadAssets = async ({ channelId, assetsIds }: ChannelExtrinsicResult) => {
const uploadPromises: Promise<unknown>[] = []
if (avatarAsset?.blob && assetsIds.avatarPhoto) {
const uploadPromise = startFileUpload(avatarAsset.blob, {
id: assetsIds.avatarPhoto,
owner: channelId,
parentObject: {
type: 'channel',
id: channelId,
},
dimensions: data.avatar.assetDimensions ?? undefined,
imageCropData: data.avatar.imageCropData ?? undefined,
type: 'avatar',
})
uploadPromises.push(uploadPromise)
}
if (coverAsset?.blob && assetsIds.coverPhoto) {
const uploadPromise = startFileUpload(coverAsset.blob, {
id: assetsIds.coverPhoto,
owner: channelId,
parentObject: {
type: 'channel',
id: channelId,
},
dimensions: data.cover.assetDimensions ?? undefined,
imageCropData: data.cover.imageCropData ?? undefined,
type: 'cover',
})
uploadPromises.push(uploadPromise)
}
Promise.all(uploadPromises).catch((e) =>
SentryLogger.error('Unexpected upload failure', 'CreateEditChannelView', e)
)
}
const refetchDataAndUploadAssets = async (result: ChannelExtrinsicResult) => {
const { channelId, assetsIds } = result
if (assetsIds.avatarPhoto && avatarAsset?.url) {
addAsset(assetsIds.avatarPhoto, { url: avatarAsset.url })
}
if (assetsIds.coverPhoto && coverAsset?.url) {
addAsset(assetsIds.coverPhoto, { url: coverAsset.url })
}
if (newChannel) {
// add channel to new channels list before refetching membership to make sure UploadsManager doesn't complain about missing assets
addNewChannelIdToUploadsStore(channelId)
}
const refetchPromiseList = [refetchActiveMembership(), ...(!newChannel ? [refetchChannel()] : [])]
await Promise.all(refetchPromiseList)
if (newChannel) {
// when creating a channel, refetch operators before uploading so that storage bag assignments gets populated for a new channel
setActiveUser({ channelId })
fetchOperators().then(() => {
uploadAssets(result)
})
} else {
uploadAssets(result)
}
}
const completed = await handleTransaction({
preProcess: processAssets,
txFactory: async (updateStatus) =>
newChannel
? (await joystream.extrinsics).createChannel(activeMemberId, metadata, assets, proxyCallback(updateStatus))
: (
await joystream.extrinsics
).updateChannel(activeChannelId ?? '', activeMemberId, metadata, assets, proxyCallback(updateStatus)),
onTxSync: refetchDataAndUploadAssets,
})
if (completed && newChannel) {
navigate(absoluteRoutes.studio.videos())
}
}
if (error) {
return <ViewErrorFallback />
}
const progressDrawerSteps = [
{
title: 'Add channel title',
completed: !!dirtyFields.title,
onClick: () => setFocus('title'),
},
{
title: 'Add description',
completed: !!dirtyFields.description,
onClick: () => setFocus('description'),
},
{
title: 'Add avatar image',
completed: !!dirtyFields.avatar,
onClick: () => avatarDialogRef.current?.open(),
},
{
title: 'Add cover image',
completed: !!dirtyFields.cover,
onClick: () => coverDialogRef.current?.open(),
},
]
const hasAvatarUploadFailed = (channel?.avatarPhoto && !channel.avatarPhoto.isAccepted) || false
const hasCoverUploadFailed = (channel?.coverPhoto && !channel.coverPhoto.isAccepted) || false
const isDisabled = !isDirty || nodeConnectionStatus !== 'connected' || !isValid
return (
<form onSubmit={handleSubmit}>
{headTags}
<Controller
name="cover"
control={control}
render={() => (
<>
<ChannelCover
assetUrl={loading ? null : coverAsset?.url}
hasCoverUploadFailed={hasCoverUploadFailed}
onCoverEditClick={() => coverDialogRef.current?.open()}
editable
disabled={loading}
/>
<ImageCropModal
imageType="cover"
onConfirm={handleCoverChange}
onError={() =>
displaySnackbar({
title: 'Cannot load the image. Choose another.',
iconType: 'error',
})
}
ref={coverDialogRef}
/>
</>
)}
/>
<StyledTitleSection className={transitions.names.slide}>
<Controller
name="avatar"
control={control}
render={() => (
<>
<StyledAvatar
assetUrl={avatarAsset?.url}
hasAvatarUploadFailed={hasAvatarUploadFailed}
size="fill"
onClick={() => avatarDialogRef.current?.open()}
editable
loading={loading}
/>
<ImageCropModal
imageType="avatar"
onConfirm={handleAvatarChange}
onError={() =>
displaySnackbar({
title: 'Cannot load the image. Choose another.',
iconType: 'error',
})
}
ref={avatarDialogRef}
/>
</>
)}
/>
<TitleContainer>
{!loading || newChannel ? (
<>
<Controller
name="title"
control={control}
rules={textFieldValidation({ name: 'Channel name', minLength: 3, maxLength: 40, required: true })}
render={({ field: { value, onChange } }) => (
<Tooltip text="Click to edit channel title" placement="top-start">
<StyledTitleArea min={3} max={40} placeholder="Channel title" value={value} onChange={onChange} />
</Tooltip>
)}
/>
{!newChannel && (
<StyledSubTitle variant="t200">
{channel?.follows ? formatNumberShort(channel.follows) : 0} Followers
</StyledSubTitle>
)}
</>
) : (
<>
<TitleSkeletonLoader />
<SubTitleSkeletonLoader />
</>
)}
</TitleContainer>
</StyledTitleSection>
<LimitedWidthContainer>
<InnerFormContainer actionBarHeight={actionBarBoundsHeight}>
<FormField title="Description">
<Tooltip text="Click to edit channel description">
<TextArea
placeholder="Description of your channel to share with your audience"
rows={8}
{...register(
'description',
textFieldValidation({ name: 'Description', minLength: 3, maxLength: 1000 })
)}
maxLength={1000}
error={!!errors.description}
helperText={errors.description?.message}
/>
</Tooltip>
</FormField>
<FormField title="Language" description="Main language of the content you publish on your channel">
<Controller
name="language"
control={control}
rules={requiredValidation('Language')}
render={({ field: { value, onChange } }) => (
<Select
items={languages}
disabled={loading}
value={value}
onChange={onChange}
error={!!errors.language && !value}
helperText={(errors.language as FieldError)?.message}
/>
)}
/>
</FormField>
<FormField
title="Privacy"
description="Privacy of your channel. Please note that because of nature of the blockchain, even unlisted channels can be publicly visible by querying the blockchain data."
>
<Controller
name="isPublic"
control={control}
render={({ field: { value, onChange } }) => (
<Select
items={PUBLIC_SELECT_ITEMS}
disabled={loading}
value={value}
onChange={onChange}
error={!!errors.isPublic && !value}
helperText={(errors.isPublic as FieldError)?.message}
/>
)}
/>
</FormField>
<CSSTransition
in={!isWorkspaceOpen}
timeout={2 * parseInt(transitions.timings.loading)}
classNames={transitions.names.fade}
unmountOnExit
>
<ActionBarTransactionWrapper ref={actionBarRef}>
{!activeChannelId && progressDrawerSteps?.length ? (
<StyledProgressDrawer steps={progressDrawerSteps} />
) : null}
<ActionBar
primaryText="Fee: 0 Joy"
variant={newChannel ? 'new' : 'edit'}
secondaryText="For the time being no fees are required for blockchain transactions. This will change in the future."
primaryButton={{
text: newChannel ? 'Create channel' : 'Publish changes',
disabled: isDisabled,
onClick: handleSubmit,
tooltip: isDisabled
? {
headerText: newChannel
? 'Fill all required fields to proceed'
: isValid
? 'Change anything to proceed'
: 'Fill all required fields to proceed',
text: newChannel
? 'Required: title'
: isValid
? 'To publish changes you have to provide new value to any field'
: 'Required: title',
icon: true,
}
: undefined,
}}
secondaryButton={{
visible: !newChannel && isDirty && nodeConnectionStatus === 'connected',
text: 'Cancel',
onClick: () => reset(),
}}
/>
</ActionBarTransactionWrapper>
</CSSTransition>
</InnerFormContainer>
</LimitedWidthContainer>
</form>
)
}
Example #8
Source File: LabeledInput.tsx From UsTaxes with GNU Affero General Public License v3.0 | 4 votes |
export function LabeledInput<TFormValues>(
props: LabeledInputProps<TFormValues>
): ReactElement {
const { onSubmit } = useFormContainer()
const { label, patternConfig: patternConfigDefined, name, rules = {} } = props
const { required = patternConfigDefined !== undefined } = props
const {
autofocus,
patternConfig = Patterns.plain,
useGrid = true,
sizes = { xs: 12 }
} = props
const classes = useStyles()
const inputRef = useRef<HTMLElement | null>(null)
useEffect(() => {
if (autofocus && inputRef.current) {
inputRef.current.focus()
}
}, [inputRef.current])
const {
control,
handleSubmit,
register,
formState: { errors }
} = useFormContext<TFormValues>()
const error: FieldError | undefined = getNestedValue(errors, name, undefined)
const errorMessage: string | undefined = (() => {
if (error?.message !== undefined && error.message !== '') {
return error.message
}
if (isNumeric(patternConfig)) {
if (error?.type === 'max' && patternConfig.max !== undefined) {
return `Input must be less than or equal to ${
patternConfig.prefix ?? ''
}${patternConfig.max}`
}
if (error?.type === 'min' && patternConfig.min !== undefined) {
return `Input must be greater than or equal to ${
patternConfig.prefix ?? ''
}${patternConfig.min}`
}
}
})()
const input: ReactElement = (() => {
if (isNumeric(patternConfig)) {
return (
<Controller
name={name}
control={control}
render={({ field: { name, onChange, ref, value } }) => (
<NumberFormat
customInput={TextField}
inputRef={autofocus ? useForkRef(ref, inputRef) : ref}
id={name}
name={name}
className={classes.root}
label={label}
mask={patternConfig.mask}
thousandSeparator={patternConfig.thousandSeparator}
// prefix={patternConfig.prefix}
allowEmptyFormatting={true}
format={patternConfig.format}
isNumericString={false}
onValueChange={(v) => onChange(v.value)}
value={value as number}
error={error !== undefined}
fullWidth
helperText={errorMessage}
variant="filled"
InputLabelProps={{
shrink: true
}}
InputProps={{
startAdornment: patternConfig.prefix ? (
<InputAdornment position="start">
{patternConfig.prefix}
</InputAdornment>
) : undefined,
onKeyDown: (e: KeyboardEvent) => {
if (e.key === 'Enter') {
onSubmit?.()
void handleSubmit(() => {
//do nothing
})()
}
}
}}
/>
)}
rules={{
...rules,
min: patternConfig.min,
max: patternConfig.max,
required: required ? 'Input is required' : undefined,
pattern: {
value: patternConfig.regexp ?? (required ? /.+/ : /.*/),
message:
patternConfig.description ??
(required ? 'Input is required' : '')
}
}}
/>
)
}
return (
<Controller
control={control}
name={name}
render={({ field: { name, onChange, ref, value } }) => (
<TextField
{...register(name, {
...rules,
required: required ? 'Input is required' : undefined,
pattern: {
value: patternConfig.regexp ?? (required ? /.+/ : /.*/),
message:
patternConfig.description ??
(required ? 'Input is required' : '')
}
})}
inputRef={autofocus ? useForkRef(ref, inputRef) : ref}
id={name}
name={name}
className={classes.root}
label={label}
value={value}
onChange={onChange}
onKeyDown={(e) => {
if (e.key === 'Enter') {
void handleSubmit(() => {
// do nothing
})()
onSubmit?.()
}
}}
fullWidth
helperText={error?.message}
error={error !== undefined}
variant="filled"
InputLabelProps={{
shrink: true
}}
/>
)}
/>
)
})()
return (
<ConditionallyWrap
condition={useGrid}
wrapper={(children) => (
<Grid item {...sizes}>
{children}
</Grid>
)}
>
{input}
</ConditionallyWrap>
)
}