use crate::prelude::*;
use dioxus::prelude::*;
#[derive(Store, PartialEq, Eq, Debug)]
struct TestData {
name: String,
}
#[derive(Clone, Store, Default)]
struct TestFieldState {
name: FieldState<String, FormError>,
}
#[derive(Store, Clone, PartialEq)]
struct TestForm {
field_state: Store<TestFieldState>,
form_state: Store<FormState>,
}
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(),
})
}
}
impl GetFieldRegistry for TestForm {
type FieldRegistry = Self;
fn get_field_registry(&self) -> Self::FieldRegistry {
self.clone()
}
}
#[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()
}
fn name(&self, field: &GetName) -> &'static str {
"name"
}
}
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;
}
}
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 = use_init_form_state();
let field_state = use_store(|| TestFieldState::default());
Self {
field_state,
form_state,
}
}
}
#[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
}
}
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 {})
}
}
#[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
}
}
}
#[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 }}
}
}
}
}