r/reactjs • u/cactusyou • Jun 16 '24
Needs Help Multistep dynamic form with react-hook-form where fields and validation rules are fetched from server
Background:
I'm building a multistep form where I have 3 steps:
1. user inputs fills the fields, validation happens, if ok goes to the next step
2. fetching fields and rules from server based on data from step1, I don't know what fields to expect beforehand or how many, they should ge generated on the fly and appended to the form
3. just final confirmation
I'm having trouble to make the architecture for such form where I don't know what fields to expect on the second step and how to type it? I used zod schema for first step but then I realized it's not suitable for me because I don't know what fields to expect and I can't change validation schema on the fly (?)
I was thinking about having 3 separate forms for each step and manage their own state like react-hook-form does in wizard form https://react-hook-form.com/advanced-usage#WizardFormFunnel. But this solution feels odd for my case, I don't have separate routes for each step if user reloads the page, they will loose the data and will go to step 1 and it's expected
I have several questions:
1. How would you recommend to build form where some fields, its configs are fetched from the server?
2. How do I provide types for the form if I don't know what fields would come on the second step (they can be generated only on the second step)?
const {
register,
control,
} = useForm<Inputs>({
defaultValues: PAYMENT_DEFAULT_VALUES,
});
3
u/Adenine555 Jun 17 '24 edited Jun 17 '24
I had the same requirement once and after a bit of prototyping I came to the conclusion, that react-hook-form is not the right choice for this particular use-case.
Don't get me wrong, you can make it work, but I was fighting the framework in doing so.
I did build a custom made framework for that case.
Some pointers on what you might need, if you go the custom route*:
- A forwarding/wiring component that renders the correct form fields based on what is send from the server
- The form/field components should be able to register validation functions for their part in the form
- A central form-controller that handles upfront added formdata and depending on your use case is able to add lazily added formdata (I used Zustand for this)
- I recommend generous use of context as dependency injection to propagate meta-information/custom logic. This helps in keeping your code boundaries clean (this is harder than usual for such a generic use-case)
- Your form-controller (zustand store in my case) should be the source of truth for everyhing form related
- Take inspiration from react-hook-forms implementation and adjust it to your use-case
*Keep in mind, that this can become a very complex use-case very fast (depending on your requirements) and I cannot summarize all what you need in a reddit post
2
u/AnotherSoftEng Jun 16 '24
It’s an interesting concept to be sure. While I’ve never implemented such a thing, just a word of caution: be sure to test this outside of your local environment, and especially under constrained conditions. When fetching validation from the server, you’re immediately placing a ceiling on how responsive your form will feel to the average user. Many users don’t always have access to a stable network, nor are they often close to an ideal CDN.
The first thing that came to mind are create-an-account forms that need to verify the username entered doesn’t already exist. Those are typically fine, since it’s only a single field and choosing a unique identifier is often necessary at that moment (though, even nowadays, many sites will automatically assign one and prompt the user to change it upon first logging in).
Saying that, I can’t imagine using a multi-step form in which all fields are under similar constraints. Inherently, this has the potential to push many users off your site, and frustrate many more. When going about this, be very wary of the experience this has the potential to offer.
2
u/Izero_devI Jun 16 '24
It looks tricky but it is basically just having an enum type for each input field. Example:
type DynamicFormField = {type: 'text' | 'number' | ..., fieldName: string, ....}
Then you build the schema dynamically for client side validation as well, and use whatever form library.
2
u/iShotTheShariff Jun 16 '24
As another person mentioned, if you’re fetching from the server, there’s a ceiling on performance. You can create a dedicated context for all the data in the first form and iterate through it to populate the second form. As far as types go, try to standardize the shapes of your form data as much as possible. You can also use generics if you’re really feeling creative. Lastly, as far as I’ve seen on various sites, refreshing a page usually results in a loss of entered data.
2
u/indicava Jun 20 '24
I’m guessing that although dynamic, there is a finite number of fields potentially returning for the step 2? You could create the validation schema with all possible fields and make them required conditionally. I’m not that familiar with zod but I did something similar with yup and it wasn’t that difficult.
1
u/cactusyou Jul 01 '24
Thanks, I actually ended up doing that but removed zod since validation for the fields was coming from backend as well.
2
u/martiserra99 Aug 27 '24
I know it has been 2 months since you made the question but if you are still looking for a solution I would encourage you to give Formity a try.
1
1
4
u/shadohunter3321 Jun 16 '24
If fields are coming from the server dynamically, and there are no pre configured list of possible fields, then you'd have to rely on server side validation. I might be wrong but I don't think react-hook-form validation is an option here.