New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Proposal to improve Validation plugin #94
Comments
Hi, I agree that the syntax you propose is good. But before we go further, I I would like to understand the problem you faced. Could you please post a bit more complete example which I could play with and reproduce the issue? Thanks, |
my goal is to have tabs, that can validate themselves, and the parent can show error badges based on the children. const state = useState({});
Validation(a.array1[0].name).validate(a => a.length > 0, 'Name required');
Validation(a.array2[0].name).validate(a => a.length > 0, 'Name required');
return (
<Tabs>
<Tab error={() => Validation(useState(state).array1).valid()}>
<ManageArray1 state={state} />
</Tab>
<Tab error={() => Validation(useState(state).array2).valid()}>
<ManageArray2 state={state} />
</Tab>
</Tabs>
); |
What is this one expanded to:
Is the useState here invoked within a child component of a Tab component? |
And how do you update the state? Do you change names? Add more items to an array? |
yes, useState is invoked in child component which isn't in same render tree as ManageArray, etc state could be updated in an arbitrary way... for sake of this example, assume i have a form downstream which deletes the name value and causes valid() to equal false |
"deletes the name". Means sets it to empty string?
…On Fri, Aug 14, 2020 at 11:46 AM Paris Holley ***@***.***> wrote:
yes, useState is invoked in child component which isn't in same render
tree as ManageArray, etc
state could be updated in an arbitrary way... for sake of this example,
assume i have a form downstream which deletes the name value and causes
valid() to equal false
—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
<#94 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AA6JSVO2AC4BTA4SLO7HVTDSAR3LTANCNFSM4P6MH7YQ>
.
|
yes |
Ok. Validation plugin requires greater improvements to leverage API v3. It still uses the style of the v1, when it was originally written. One of the improvements is to allow to define validation rules on attach, for example eg: As a workaround, try to attach the rule for name length within the tab child component, which uses the scoped state. It should reduce touching of the array by a parent component. Can you try this? |
realized when digging further, is my [0] method only works if the array is populated, otherwise I get see what i can do about re-writing plugin |
gonna work on implementation, but so far, thinking typing would look like this: Validation(state).name(name => name && name.length > 0);
Validation(state.name).validate(name => name && name.length > 0);
Validation(state.properties).forEach((property) => {
property.id(id => id && id.length > 0);
property.values.validate((values => values.length > 0));
property.values.forEach((value) => {
value.id(id => id && id.length > 0);
});
}); depending on the type of State object you pass in, you'll get a builder back that emulates the state graph |
Hi, I have been thinking about the plugin your are proposing. One thing would be good to check is how your plugin adds new validation rules when rerender happens. If a component is rerendered, do you have the same validation rule added twice? Overall, I understand what you would like to accomplish in code. I do not have major code related comments without trying your plugin with a real app, but I can not do this now as busy with other projects. But I will have an opportunity and needs to validate states in the coming projects for sure, or maybe I will refactor one of my older apps. So, I suggest the following plan for your contribution:
How does it sound? |
Yeh, the default behavior would be to re-register (duplicate) the validation, so it is up to the user to move the validation high up the stack where their initial state lives (as the intent is to model your state/validation upfront, which should be consistent regardless of re-render's down stream), or to wrap in useEffect/useMemo/useCallback. Effectively, this plugin is intended for traditional DTO patterns of known models and not on-the-fly validation generation, which is why I'm thinking it may more appropriately be called a Form (or Form Validation) plugin, not "validation". Doing a de-dupe check would be tough because of the closures. I suppose it is plausible to hash/toString() the closures but I'm not sure how reliable that would be. Maybe the plugin could abstract the call to useMemo(), using the state (or some state property, such as path) as a memo value, and only registering once per state. As a version one, may just keep it simple an enforce a single-validator per path, and throw an exception if it is already registered so that the user knows they need to handle a re-render case on their end. 1-5 works for me. I'll be pushing this plugin through a production app over the coming weeks, so i'll vet it a bit more before making it an official plugin, but would be good to at least release it in an alpha for people to use if they choose. |
It means that the validation should be either attached in global space or on plugin attach (see below for more details).
Form state can be also local state. The philosophy of the Hookstate is not not force a decision for a user which variable to use - local or global.
The current plugin works like this. I found it is reliable. But better to attach validation upfront once (either global or on attach)
The plugin does not need useMemo, the core provides the required functionality. There is a callback in the plugin init: https://hookstate.js.org/docs/typedoc-hookstate-core#optional-readonly-init
I suggest you do this for your plugin. This way it would be possible to use your plugin with local states too (and it would follow the principles of the hookstate philosophy). In addition to the above (as an optional thing), this approach of "validation model on attach" would allow you do drop proxy builder, because a user can return just a large structure with validation rules. For your example (an array of objects), it would be like: const state = useState({}).attach(Validation(() => {
const validators = ((s) => { /* validator for the root state if any */ }) as ValidationModel<YourRootStateTypeHere> // I can give you the definition of the ValidationModel type, which would allow strong type checking for validation rules at any level, it will mirror the structure of YourRootStateTypeHere
validators['*'] = (s) => { /* validator for each element of an array */ }
validators[0] = (s) => { /* validator for the 1st element of an array */ }
validators['*'].name = (s) => { /* validator for the nested name property of each element of an array */ }
return validators
})) This is what I am hoping to come up with in the future. What do you think? |
i haven't looked into putting all of the validation into the attach() call, but that seems sensible. i'll try that after i've worked through all of my implementations to make sure there aren't any gotchas. my issue with going the simple object route is the following isn't type checked by compiler while in theory you could require the validation object to follow the it can't intuitively handle conditional fields This is a big reason why validation state needs to be defined high up the stack, because you still want to validate the object, even if the components which managed substate downstream aren't rendered. That being said, I have tested assigning validation rules in downstream components, you just have to decide on always rendering all subcomponents. Validation(state.message).required();
Validation(state).whenType('__typename', 'NotificationConfigEmail').subject.required(); complex form dependencies cannot be modeled intuitively in raw json if you have form fields that are layers deep, and a toggle in the middle of the stack, changes validation behavior in fields downstream in the stack, you need a clean way to only listen to the fields you care about. const enabled = Validation(state.notifications).when(n => n.enabled.get());
enabled.message.required();
enabled.whenType('__typename', 'NotificationConfigEmail').subject.required();
Validation(state).when(s => s.notificationTimeEnabled.get()).notifications.time.required(); I'm not sure how you could intuitively model {
enabled: {
condition: (value: boolean) => {
if (value){
return {
notifications: {
message: {
required: true
}
}
}
}
}
}
} That could in theory allow you to require a message, if the notification is enabled, but what happens when you need to depend on two different fields? {
field1: {
condition: (value: boolean) => {
if (value){
return {
notifications: {
message: {
required: true
}
}
}
}
}
},
field2: {
condition: (value: boolean) => {
if (value){
return {
notifications: {
message: {
required: true
}
}
}
}
}
}
} How would the validator behave in that case? Is it suppose to merge? Do we expect both to be true before enabling the require condition? What happens if they return conflicting validations? I find the fluent API is easier to understand and reason about, and makes dynamic validation a lot easier to work with (especially if you want non-form state to impact which rules are live, simple enough to just wrap validation rules in a if condition) |
oh, so one of the reasons I didn't like using the state.attach(Validation) approach, is you can't infer the type of the State when doing that... to make that work, I think there would need to be a |
Hookstate-4 has validation plugin reworked. In combination with the Initializable extension, it is possible to run the code once per created state, for example attach validation rules only once. The ability to combine extensions, add new extension methods and override existing methods gives all the required bits to implement custom variations of the validation extensions. The documentation update will follow soon. |
I was initially stuck because I wanted my navigation to show badges when "tabs" had validation issues, but didn't want to re-render all the tabs on change. Initially I was doing
The problem with the above though is it will now subscribe my tab/navigation to any validation failures and cause a full re-render of all children.
Once I dug into the code I realized I could write it like this:
Which actually ends up wildcarding behind the scenes, but not clear from the syntax. At the very least would be helpful to document this current behavior, but I think a more idiomatic syntax would be:
The text was updated successfully, but these errors were encountered: