ridstack-form 0.1.0

End-to-End Type-safe form handling for Dioxus applications
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
# Ridstack-Form


Type-safe form handling for Dioxus.

## Quickstart

```rust, no_run
use dioxus::prelude::*;
use ridstack_form::prelude::*;
use thiserror::Error;
fn main() {
    dioxus::launch(App);
}

// reusable component
#[component]

fn TextField<TForm, TField>(
    #[props(optional)] id: String,
    field_api: ReadSignal<FieldApi<TForm, TField>>,
    #[props(extends=GlobalAttributes,extends=input)] attributes: Vec<Attribute>,
) -> Element
where
    TForm: FieldStateProvider<TField>,
    TField: Clone + PartialEq + 'static,
    TForm::FieldValue: FieldValue<PrimitiveValue = String>,
{
    rsx! {
        label{
            r#for: &id
        }
        input{
            id,
            value: field_api().value(),
            onblur: field_api.peek().handlers().on_blur(),
            oninput: move|evt|{
                let _ = field_api.peek().handlers().on_input()(Either::Right(evt.value()));

            },
            ..attributes
        }
        div{if let Some(err) = (field_api().state().errors)().get(0){

            p{
                {err.error_value()}
            }
        }
        }
    }
}

#[derive(Store, GenForm, PartialEq, Eq)]

struct Example {
    // You can skip generation of some fields by removing this attribute.
    #[field(error(FormError), accessor = "GetName")]
    name: String,
    // other fields...
}

#[derive(Debug, Error, PartialEq, Eq, PartialOrd, Ord, Clone)]

enum FormError {
    #[error("This error should never occur.")]
    Infallible,
}
impl FieldError for FormError {}
impl From<Infallible> for FormError {
    fn from(_value: Infallible) -> Self {
        Self::Infallible
    }
}

#[component]

fn App() -> Element {
    rsx! {

            ExampleForm {
                on_submit: |_data|{},
                ExampleField {
                    field:GetName,
                    component:|field_api|{
                        rsx! {
                            TextField {  field_api}
                        }
                    }
                }
            }

    }
}



```
## Status

<ul>
    <li> Validify feature is still in WIP. So it should be disabled.
</li>
<li>Tests aren't present as I'm too lazy to write some but I also have
    a lot of other work. The ones in the test folder aren't really tests but a blueprint to write this Readme.
<li>Ready to be used just docs and readme needs to be updated.</li>
</ul>

## Let's Deep Dive


### Architecture

Check [`architecture.svg`](./architecture.svg) in the repo.

### Field Value

For a field to work, the value of that field must apply `FieldValue` trait.
Every `FieldValue` needs to point a primitve field value which is basically `String`, `u8`, `u16`, `bool`, etc.

The primitive field value is required for the components to get data of the field.
Interconversion btw field and primitive field value is needed.
By default `FieldValue` and `PrimitiveFieldValue` is applied for most of rust built-in types.

You can also apply `PrimitiveFieldValue` on your own struct or enum. Then create components
accessing it as field data.

There's also a macro named `FieldValue` which builds `FieldValue` trait for wrapper-like structs.
It is helpful to auto build `FieldValue` if the underlying data implements `PrimitiveFieldValue`.


### Reusable Components

Ridstack form is made on the principle of reusable components. It means you've to make components
or parts of your form only once and you can use it as many times as you want (like Tanstack Form).
You can make a component work only on a particular type of field. (Its based on the `PrimitiveFieldValue` type).
An example is here:
```rust, no_run
use dioxus::prelude::*;
use ridstack_form::prelude::*;
#[component]

fn TextField<TForm, TField>(
    #[props(optional)] id: String,
    field_api: ReadSignal<FieldApi<TForm, TField>>,
    #[props(extends=GlobalAttributes,extends=input)] attributes: Vec<Attribute>,
) -> Element
where
    TForm: FieldStateProvider<TField>,
    TField: Clone + PartialEq + 'static,
    TForm::FieldValue: FieldValue<PrimitiveValue = String>,
{
    rsx! {
        label{
            r#for: &id
        }
        input{
            id,
            value: field_api().value(),
            onblur: field_api.peek().handlers().on_blur(),
            oninput: move|evt|{
                let _ = field_api.peek().handlers().on_input()(Either::Right(evt.value()));

            },
            ..attributes
        }
        div{if let Some(err) = (field_api().state().errors)().get(0){

            p{
                {err.error_value()}
            }
        }
        }
    }
}
```
Here in the form generics, it's specified that only those types whose `PrimitiveFieldValue` is of `String` type.
In this way the type-safety of reusable components is maintained.

For your reference, I'm putting how to make a good components system in a separate dir (`components` dir) inside the **repo's** example dir.
If you want raw errors (errors in `String` form), you can use the `use_raw_field_errors` hook, which will directly return the 
error of highest priority, maintained by `Ord` and `PartialOrd` (0 is the hightest, 1 next, ...) in form of `String`.
**Note: This is directly copied from my project and thus many packages are unspecified.**
**Note: Ensure that `use_raw_field_errors` should be used inside Field component or its children as it may give a context error.**

### Manual Setup

1. Create your data struct.
```rust, no_run
#[derive(Store, PartialEq, Eq)]
struct TestData {
    name: String,
    // more fields...
}
```
2. Create error details for the fields for the form.
it should handle this infallible error returned from string.
and these derive traits are also required.
You can also use many different types of error.
Basically these errors will be used for validation which
will be done by you only.
```rust,no_run
#[derive(Debug, thiserror::Error, PartialEq, Eq, PartialOrd, Ord, Clone)]
enum FormError {
    #[error("This error should never occur.")]
    Infallible,
    #[error("Name already exists.")]
    NameAlreadyExists,
    #[error("Name shouldn't start with R")]
    NameShouldntStartWithR
}
impl FieldError for FormError {}
impl From<Infallible> for FormError {
    fn from(_value: Infallible) -> Self {
        Self::Infallible
    }
}
```
3. Create a struct for storing your fields. Use Store macro on it.
```rust, no_run
#[derive(Clone, Store, Default)]
struct TestFieldState {
    name: FieldState<String, FormError>,
    // more fields...
}
```

4. Create form state. Its advised (and mandatory also),
to use Store on the `TestFieldState` and `FormState`.
```rust,no_run
#[derive(Store, Clone)]
struct TestForm {

    field_state: Store<TestFieldState>,
    // required, for managing the form state.
    form_state: Store<FormState>,
}
```

5. Let's start applying traits.
    1. `FormSubmitInput` returns the original data you've created.
    ```rust,no_run
    impl FormSubmitInput for TestForm {
        type SubmitInput = TestData;
        fn build_submit_input(&mut self) -> Option<Self::SubmitInput> {
            Some(TestData {
                name: self.field_state.name().data().peek().cloned(),
            })
        }
    }
    ```

    2. `GetFieldRegistry` returns the field_registry 
    ```rust,no_run
    impl GetFieldRegistry for TestForm {
        type FieldRegistry = Self;
        fn get_field_registry(&self) -> Self::FieldRegistry {
            self.clone()
        }
    }
    ```
    3. Here's the important part. Each field of the form needs an accessor
        which is mostly (there are other patterns also) an empty struct with some derive impls.
        Then we apply the `FieldStateProvider` taking this accessor as generic over the `FieldRegistry`.
        We could've returned and made the `TestFieldState` the `FieldStateProvider` but the API doesn't allow 
        us and also it may break granular reactivity provided by the store.
    ```rust,no_run
    #[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
    struct GetName;
    
    impl FieldStateProvider<GetName> for TestForm {
        type FieldValue = String;
        type FieldError = FormError;
        fn get_field_state(
            &self,
            field: &GetName,
        ) -> Store<FieldState<Self::FieldValue, Self::FieldError>> {
            self.field_state.name().into()
        }
        // name attr of the form element.
        fn name(&self, field: &GetName) -> &'static str {
            "name"
        }
    }
    ```
    4. Now, we apply calculate can submit because it can't be auto-checked
        whether the fields are ready to be submitted or not.
    ```rust,no_run
    impl CalculateCanSubmit for TestForm {
        fn calculate_can_submit(&mut self) -> bool {
            let name_can_submit = self.field_state.name().can_submit();
            let name_is_validating = self.field_state.name().is_validating()();
            if !name_can_submit || name_is_validating {
                self.form_state.can_submit().set(false);
                return false;
            }
            return true;
        }
    }
    ```
    5. We will apply some traits required for Form management.
        The first is `GetFormState` which makes us return the form state.
        The second is `InitForm` which returns a hook used to initialize the form
    ```rust,no_run
    impl GetFormState for TestForm {
        fn get_form_state(&self) -> Store<FormState> {
            self.form_state.clone()
        }
    }
    
    impl InitForm for TestForm {
        fn use_form() -> Self {
            let form_state = FormState::use_form_state();
            let field_state = use_store(|| TestFieldState::default());
            Self {
                field_state,
                form_state,
            }
        }
    }
    ```
    6. We need to manually check which field validators to be run on submit. For this we apply `ValidateOnSubmit` trait.
    ```rust,no_run
    impl ValidateOnSubmit for TestForm {
        fn validate_on_submit(&mut self) -> std::pin::Pin<Box<impl Future<Output = ()>>> {
            if matches!(
                self.field_state.name().validate_on()(),
                ValidateOn::All | ValidateOn::Submit
            ) {
                self.field_state.name().validate();
            }
            Box::pin(async move {})
        }
    }
    
    ```
    7. Create some components shown above. We mostly don't directly write component
        as we don't get type safety in the arguments of the closure.
    8. Now the final piece. That's using this form. To get started, use the `use_form_provider` hook and
        pass your form type (here `TestForm`) in the turbofish operator.
    9. Then use the `Form` component to provide states to the child. This is the power that if you use modals
        or steppers, the form and field states will be preserved due to direct entry to the stores.
    10. Then use the `Field` component to access the fields. Use the accessor structs to ensure that the `Field` component
        points to the specified field. You can use the `validators` and `async_validators` prop to set sync and async validators 
        respectively. You can also use the `validate_on` prop to make sure that validators run before submit, on blur, on input or all.
   
        **Note: `Form` and `Field` components need heavy generics of the Form struct (here `TestForm`). It is a better practice to already 
        put the generics in a separate component and use that instead.**
    Here's the last part.
    ```rust, no_run
    
    #[component]
    fn TestFormComponent(on_submit: EventHandler<TestData>, children: Element) -> Element {
        rsx! {
            Form::<TestForm> {
                on_submit,
                {children}
            }
        }
    }
    
    #[component]
    fn TestFieldComponent<TField>(
        component: Callback<FieldApi<TestForm, TField>, Element>,
        field: TField,
    
        #[props(optional)] validate_on: ValidateOn,
        #[props(optional, default = Vec::with_capacity(0))] validators: Validators<
            <<TestForm as GetFieldRegistry>::FieldRegistry as FieldStateProvider<TField>>::FieldValue,
            <<TestForm as GetFieldRegistry>::FieldRegistry as FieldStateProvider<TField>>::FieldError,
        >,
        #[props(optional, default = Vec::with_capacity(0))] async_validators: AsyncValidators<
            <<TestForm as GetFieldRegistry>::FieldRegistry as FieldStateProvider<TField>>::FieldValue,
            <<TestForm as GetFieldRegistry>::FieldRegistry as FieldStateProvider<TField>>::FieldError,
        >,
    ) -> Element
    where
        TField: Clone + PartialEq + 'static,
        TestForm: FieldStateProvider<TField>,
    {
        rsx! {
            Field::<TestForm, TField> {
                validate_on,
                validators,
                async_validators,
                field,
                component
            }
        }
    }
    
    // reusable component
    #[component]
    fn TextField<TForm, TField>(
        #[props(optional)] id: String,
        field_api: ReadSignal<FieldApi<TForm, TField>>,
        #[props(extends=GlobalAttributes,extends=input)] attributes: Vec<Attribute>,
    ) -> Element
    where
        TForm: FieldStateProvider<TField>,
        TField: Clone + PartialEq + 'static,
        TForm::FieldValue: FieldValue<PrimitiveValue = String>,
    {
        rsx! {
            label{
                r#for: &id
            }
            input{
                id,
                value: field_api().value(),
                onblur: field_api.peek().handlers().on_blur(),
                oninput: move|evt|{
                    let _ = field_api.peek().handlers().on_input()(Either::Right(evt.value()));
    
                },
                ..attributes
            }
            div{if let Some(err) = (field_api().state().errors)().get(0){
    
                p{
                    {err.error_value()}
                }
            }
            }
        }
    }
    
    #[component]
    fn Test() -> Element {
        use_form_provider::<TestForm>();
        rsx! {
            TestFormComponent {
                on_submit:|d|{
                    print!("{:?}", d);
                },
                TestFieldComponent {
                    field: GetName,
                    validate_on: ValidateOn::Input,
                    validators: vec![Validator::new(|input:&String|{
                        if input.starts_with("R"){
                            return Err(FormError::NameShouldntStartWithR)
                        }
                        Ok(())
                    }, 0)],
                    async_validators: vec![AsyncValidator::new(|input:&String|{
    
                        let input = input.clone();
                        Box::pin(async move {
                            if input.eq(&"John Doe".to_string()) {
                                return Err(FormError::NameAlreadyExists);
                            }
                            Ok(())
                        })
                    }, 0)],
                    component: |field_api| rsx!{TextField { field_api }}
    
                }
            }
        }
    }

    
    ```

### Some other patterns:

***Note: This section will be updated slowly***
1. If you're building form manually, you can create an enum instead of accessor structs 
  if many fields of your struct are of same type. Then use match case in `FieldStateProvider` trait, to provide
  field states. But its better to let the macro do the work.
2. If you're building form manually, then you've to use the `use_form_provider` fn to pass context down the tree.
  This opens an interesting view that if you're creating, for example someone's profile through form and
  want them to show a preview of what it may look, you can directly access the whole form through `use_form` fn
  outside the `Form` or html form component.

### FAQs

1. Was this library built with AI?

The answer is no. I don't use AI for some of my personal limitations. But some references
through AI has been definitely taken especially the async validation debounces and updates,
rest is purely built by me.


2. Why do I need this library?

Imagine you've a data-struct. You've got a dioxus server-fn taking that data as input.
Now, don't you think if you get a form having the required fields building you that data with 
all the state managements and validations built right for your data so that you can instantly
call your server-fn on its submission, won't that be very nice?

Ridstack-form makes this possible.
You take a data-struct, apply `GenForm` macro, skip the fields you don't want in your form
by doing nothing on them, and add errors and optional accessor names on the fields you want in your
form and you get all the preparations instantly. Now in part of your app, you just instantiate your form
and in the on_submit function, you get the data ready to be put in your server fn.


3. Why was this library built?

Honestly, I found no suitable form library in dioxus meeting my requirements. The form libraries
in dioxus ecosystem is bit primitive and time consuming to build. This library gets you instantly started 
(Although, there are some quirks still there), and expand accordingly. Besides its completely built with
dioxus stores thus enabling granular reactivity.


4. Why is there no tests?

First of all, I'm lazy. And since I'm still a student, I've many things to do.
I take some of my time daily to complete this.

And secondly, most of the logic is directly inside the `Field` component as it's a giant fn.
I could find any suitable way to test all of that. But the current app I'm building, I'm using it
there. If any problems arise, I'll make my best effort to solve the problem.

Or else, if you really want tests, just make a pr.


5. Is this library very verbose?

Yes, setting up a form is very tedious. 

But you don't have to worry as a giant macro is there to help you.
It'll apply all the traits by itself. You only have apply the FieldValue trait on your field data.