Best Practices for Unrepresentable Intermediate State
Hello,
I am looking for some opinions regarding best practice in the following situation.
Lets say I have a model which contains properties and some of those properties can only exist in tandem e.g. blood pressure which are usually two values. To encode this in the model the two properties are grouped into an optional property.
If I want to create a custom component which can be used to create such a model instance, I want to have three input elements. The component props would look like this:
As soon as I input
b1
or b2
I run in a case in which the model is invalid because only one of the two b-values exist. That's fine as an intermediate state but how do I represent this? I could add an extra callback onValidationFailure
and call that. But then I have this weird state in which the state in the application does not reflect the state of the UI. So I would assume that I should also call onChange(undefined)
because we do not have a valid instance at this point in time. The issue is that this would break the two-way data binding because the value is updated with undefined
and all input elements are cleared.
The other option would be to use two different models. So I have one for persistency and one which can represent all intermediate states. This results in a lot of boilerplate.
I hope my issue became kind of clear. How is this invalid intermediate state, which can not be represented by the "normal" model, handled in the components?14 Replies
hi, if I undertand the issue, it's not really different to any form validation. There's tracking the values, and validating final state. If you had a simple for m
and you can track current values like
its the same issue in a sense, unless you just start with empty string values and use just one model but then any further validation should fail
I think to accurately capture what's really going on you should go with your approach of using 2 different models, but it doesnt really have to be a lot of boilerplate. One type just represents the objects that's keeping track of value, and the other the expected value. And can be abstracted quite easily
Alternatively just use one model but make it lose enough to implement value level validation and that's where you can also keep track of errors, requirements, etc..
Hey thanks for your response. Do I understand your first approach correctly that you suggest to use
Partial<T>
for the current state of the form? If I think about it this is kind of the second model without all the boilerplate?
It probably is the same as any other form validation - But I could not find a good article / blog post / whatever which describes validation from a design perspective.yea pretty much, if you just want to declare it once, you can define the expected type for a completed form, and it follows that pretty much (~ component level initial values can be there or not) all fields are undefined at some point so partial or some deep partial type is a decent solution to broadly capture everything.
basically, the issue you outline with
b
in your interface and b1
and b2
being required for b
to be valid is a similar argument that you could make for b
and a
to be both required for SomeModel
to be valid, unless its actually possible for b
to be undefined and SomeModel
still valid. Valid in the sense that you can do something actually useful with it, like submit for use elsewhere
In terms of design there's so much you can do it really depends on what you want, but it should not matter too much, you can probably tell if it starts to suck cause using it sucks. If you see, even lots of form libraries have some issues and it shows. But you can build values against a partial type as we discussed, you can decouple it by having an array of tuples that correspond to the property and expected value type Values = {[K in keyof Model]:readonly [K,Model[K]]}[keyof Model][]
and at that point which values are in what state is still suitable for the type, and it expected logic level validation which may required runtime objects as well as types, but depends on what you wanna do with it.Okay - I will try this strategy and see how it goes.
Regarding the second paragraph: Yes I have the case that
b
is actually optional in the model. So it is fine to completely leave it undefined. Its just that if I want to define it I have to fully define it.
I guess the issue is that I am usually not a web developer. So it is a bit hard to get a feeling - But yes I asked the question because the solutions I came up with looked ugly 🙂
I am not sure what this Values
type would be. I guess I have to read up on typescript generics to decipher it.
Thanks for your help!yea of course
nice, have a look what you come up with
if it helps after can also make an example just to put it together
Do you mean that you or me create an example?
I understood the
Values
type now. It's just an array of property-names + values tuples. I am not sure in what circumstances this is better than DeepPartial<Model>
but its probably some kind of confusion on my side because I have not tried to implement this yet.yea if you think can help I can just put it as code
Its not better its just a different way of doing it, in a way its simpler as it removes the expectation of all fields being present and delegates that to logic level validation, but probably equally as good with partial / deeppartial
in most cases, if using some sort of js validation it would't matter
Hey @maxoucsgo - I finally got some time to try it out. Unfortunately I did not get very far. If you still have time it would probably help me. How would you change this example and make it less ugly? It's basically the example from the beginning - Either both numbers are given or none.
Solid Playground
Quickly discover what the solid compiler will generate from your JSX template
yea for sure
think you have to create a share link tho
cause that seems its just the playground
@maxoucsgo Whops sorry: https://playground.solidjs.com/anonymous/fa3cb624-528c-49b2-9d31-3078ca3b6925
Solid Playground
Quickly discover what the solid compiler will generate from your JSX template
@maxoucsgo Hm i could also go the zod route and create a small library which automatically generates a relaxed model - I was not able to model the behavior I want with zod. Still think I am missing something completely.
GitHub
TypeScript-first schema validation with static type inference
TypeScript-first schema validation with static type inference
yea nice, I'll make an example
but basically that's the idea, whether or not you decide to go for zod, or do something simpler that works for that case
the idea is the same of relying on runtime for validation and having the typescript represent the schema essentialy
basically something like
can become something like
or
the type is for the model and not specifially the values
had a try mocking up something to demonstrate the concept, it's a bit of overhead, especially some of the typing and there not great, but could be interesting start
but then using it is fairly nice
Solid Playground
Quickly discover what the solid compiler will generate from your JSX template
the approach is basically around schemas and stuff so probably you could find it better to just use something that already exists but this might be intersting to see the idea behind using schemas for forms