I'm making a nested form builder that is dynamic.
This is the current project I'm working on: https://codesandbox.io/s/ava-dynamic-react-hook-form-ivgt40?file=/src/App.js
The issue I'm having is that when I swap or delete Groups, the values within the Group aren't properly set.
I believe it is due to the {...register()} and the altering ref on my Input Components, but I am not certain.
The form data.js file in the linked project at the top contains the form structure I wish to render.
list: {
id,
label,
control: "list" // identifier that this element is a list
creatable: boolean,
items: [
[elements], // group 1
[elements], // group 2
]
}
input: {
id,
label,
control: "input" // identifier that this element is a input
type: "text" // type of input
defaultValue,
rules: {
required: {
value: boolean,
message: string,
}
}
}
CodePudding user response:
State management
You seem to be mixing places to that are keeping tracking of state.
You basicly have 2 types of state
- State of the form values | this is stored in the
react-hook-form
- State of the sort order of form inputs | this is stored in your
Element.jsx
By mixing these, you have to manually make sure they stay in sync
react-hook-form
When you're using react-hook-form
. you're entrusting some state management to that hook.
They provide an API through register to keep track of the fields and values for the form.
Register takes a name as the first param.
This is the only handle you have for the form to identify fields.
A new name would be a new fields.
the problem
You're not keeping things in sync.
Changing the order will do a couple of things:
- The current
elementIdPath
is something likeEmailprofile.0.notifications.0.email.0.email-abcd123
Emailprofile.0.notifications.0.email.0.email-abcd123
is passed as name to the register.- click on the change order button
handleSwapListElements
runs and changes the element viasetElementItem
- the
elementIdPath
changes:Emailprofile.0.notifications.0.email.1.email-abcd123
(notice index change from 0 to 1) - New name is registerd and treated as a new field
- the old field is also still registered and in state. clicking submit will also have the old name and value.
The fix
Keep the sort order and values in sync. You'll have to decide yourself how exactly to do that. either:
- don't include sort order or indexes as key in the name.
- manually keep things in sync using
react-hook-form
methods.
Both have their ups and downs.
not including indexes as key
A dynamic form without indexes would require you to attach all that information on the field itself.
Basically you would register a unique key (uuid), and keep track of the uuid path yourself.
IE you register to properties.:
{
"abcd123-value" : "[email protected]",
"abcd123-path" : "Emailprofile.0.notifications.0.email.0.email-abcd123" // this would not be shown to the user, but still exposed to the form, in the submit you would manually combine the information again.
}
manually keep things in sync using react-hook-form
methods.
Whenever you change the name
of a field, you would get the current value.
unregister the old name and set the value on the new name.
react-hook-form
exposes a method from the hook for that.
unregister and setValue
If it where up to me, and seeing your code.I would probably try to go in this direction as opposed to refactoring to things to remove the indexes from name.
sandbox
this sandbox with modifications should paint the picture.
It will not properly work yet for the base elements, but the nodes at profile.0.notification.0.email
should function.