I am using useFieldArray to fetch default values from my backend api. My categories is an array of strings. However, react-hook-form only supports array of objects. Here is my mongoose's schema
type BookDocument = Document & {
title: string
description: string
categories: string[]
language: string
publicationYear: number
}
const bookSchema = new Schema(
{
title: { type: String, required: true },
description: { type: String, required: true },
categories: [{ type: String, requried: true }],
language: { type: String, required: true },
publicationYear: { type: Number, required: true },
},
{ timestamps: true }
)
Therefore, from the frontend I had to modify my form as follows:
type FormData = {
title: string
description: string
categories: { category: string }[]
language: string
year: number
}
const {
handleSubmit,
control,
formState: { errors },
} = useForm<FormData>({
mode: 'onBlur',
defaultValues: {
title: book.title ?? '',
description: book.description ?? '',
categories:
book.categories.map((elem) => {
return { category: elem }
}) ?? '',
language: book.language ?? '',
year: book.publicationYear ?? '',
},
})
The problem is when call the api request. The network payload will look like this and therefore can't be sent to the backend

There is a way. Check out the code I used to trick it to return flat array:
import {
Box,
Button,
Grid,
InputLabel,
TextField,
Typography,
} from '@mui/material';
import React from 'react';
import { Controller, useFieldArray, useFormContext } from 'react-hook-form';
import InputErrorMessages from '../../../shared/InputErrorMessages';
export default function Variables() {
const {
control,
formState: {
errors,
},
} = useFormContext();
const { fields, append, remove } = useFieldArray({
name: 'variables',
control,
});
return(
<Grid item xs={12}>
<Typography variant="h6">Variables</Typography>
<Box my={2}>
<InputErrorMessages name="variables" errors={errors} />
</Box>
<Grid container spacing={2}>
{ fields.map(({ id }, index) => (
<Grid item key={id} xs={12}>
<Controller
name={`variables.${index}`}
control={control}
render={({ field }) => (
<>
<InputLabel id="size">{`CRV${index+1} =`}</InputLabel>
<TextField
onChange={field.onChange}
onBlur={field.onBlur}
inputRef={field.ref}
name={`variables.${index}.value`}
value={field.value.value || field.value}
sx={{ width: '100%' }}
/>
</>)}
/>
</Grid>
))}
<Grid item>
<Button onClick={() => append('value')}>Add Variable</Button>
</Grid>
<Grid item>
{ fields.length > 0 && (<Button onClick={() => remove(fields.length - 1)}>Delete Variable</Button>)}
</Grid>
</Grid>
</Grid>
)
}
I tricked it but not using the field item in the fields.map. Instead, I registered my own control using Controller because I want to control the name of the field and the value of it. Ie. I want to name the field variables.${index} instead of variables.${index}.value; the latter would be automatic if you used the field item returned by useFieldArray().
When you hit handleSubmit, you will see that the variable is a flat array.
However, there is a slight gotcha. If you load the form with fields from the backend, it will expect variable to be an array of objects with a value property; it won't understand a flat array of strings.
However, again, I was able to trick it because I control the value. Thus you see:
value={field.value.value || field.value}
Basically, if there is not value property on the object, then it must a string; so then just use the string.
Hope this helps!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With