Giter Site home page Giter Site logo

formstate's Introduction

FormState

Join the chat at  gitter

Form state so simple that you will fall in love ❤️

Build Status NPM version

Powered by your github ⭐s

This project is big on type safety for compile time error analysis

and autocomplete so your code reviews + code authoring + refactoring is super smooooth.

The simplest way to manage forms with mobx. Works well with any mobx- lib e.g mobx-react 🌹

Docs

We work hard on our docs, so jump here if you want to master the theory and the API 🌹

Demo

Jump here to see it used in action with code samples ❤️️

Related Work

We built FormState with our own opinions. It's totally fine if there are problems on which you hold different points with us. formstate-x is another library which offers similar API, but with some different opinions, which you may want to have a try.

formstate's People

Contributors

arrayjam avatar basarat avatar da1z avatar djwassink avatar erkieh avatar jaider avatar janpieterz avatar mrkiffie avatar nighca avatar piotrwitek avatar rebvado avatar rgdelato avatar tomaash avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

formstate's Issues

List of validators

Hello @basarat ,

In the doc you said:

The FieldState takes an optional list of validators so you would use it as simply as:

const required = (val:string) => !val && 'Value required';
const name = new FieldState('').validators(required);

I want put a list of validators for the same field like this:

const required = (val:string) => !val && 'Value required';
const only3letters  = (val:string) => !val && val.length !== 3  && 'only 3 letters';

const name = new FieldState('').validators(required).validators(only3letters);

It's possible chaining the validators? or put many validators at one time?

If you can give me an example... thanks

Add license

Just saw that the project has no license yet.
To enable people actually using this it would be great if you could add a license to the repository and get clarity on this.

FormState.compose and initial values

Hi again,
I like the idea behind the compose function of a FormState. It explains your concept of cross field validation by combining FormStates. ❤️ So, i gave it a try and found some inconsistencies concerning the initial field values.

If the initial values are empty, as in your cross field validation example, everything is fine. But when i pass a non-empty initial value, sometimes the form validation does not get triggered. Especially when the cross field validation was valid and should find an error on changing a value. Sadly, i am not sure why and when the auto validation of the form does not work.

A reproducible setup:

const form = new FormState({
  name1: new FieldState('foo').validators(nameRequired),
  name2: new FieldState('foo').validators(nameRequired),
})
  • mount the example and note that every validation passes (as it should be)
  • start changing one of the values
  • the validator does not get triggered and sets no form error
  • even on leaving the field (with a value that does not match the other field) nothing happens

FormState with ObservableArrays

Hi,
I have formState which is an observable array of collection of input field states, and I have a problem with typing the Array as Observable Array.

Here is an example:

type Fields =
  FormState<{
    conditionFieldState: FieldState<string | undefined>,
  }>;

const formState = new FormState<Fields[]>([]);

formState.$.replace(
  formState.$.filter((item, itemIndex) => itemIndex !== index),
); // Error: Property 'replace' does not exist on type 'FormState<{ conditionFieldState: FieldState<string | undefined>; }>[]'

I would like to find an elegant way to have my Array to be an ObservableArray type from mobx (thus fixing compiler error for missing replace property)

@basarat could you please help and show me some example how you would do it?

Allow validators to be specified in FieldState constructor

The FieldState constructor takes an options object, allowing things like value and auto-validation to be specified. I find it surprising that I cannot also specify validators there.

I'd prefer to write this:

username = new FieldState({
  value: '',
  validators: [required]
});

instead of this:

username = new FieldState({
  value: '',
}).validators(required);

Is there a reason why this syntax is not supported?

On submit update mobx observable

Thanks for this library. What is the correct way to access the validated form in component, so that I can update the mobx store via action.

And is this the correct way to pfill the Class with mobx store value.

Any link for sample implementation is helpful. thanks you

class DemoState {

        // Create a field
    name;
    description;
    groupname;
    timeSyncType;
    timeValue;
    timeUnit;
    fixedStartTime;
    fixedEndTime;
    form;

    constructor(dashboard: DashboardModel.DashboardView) {
        this.name = new FieldState(dashboard.dashboardName).validators((val) => !val && 'name required');
        this.description = new FieldState(dashboard.dashboardDescription);
        this.groupname = new FieldState(dashboard.groupName);
        this.timeSyncType = new FieldState('Relative');
        this.timeValue = new FieldState('8');
        this.timeUnit = new FieldState('2');
        this.fixedStartTime = new FieldState('1996-12-19T16:39:57');
        this.fixedEndTime = new FieldState('1996-12-19T16:39:57');

        this.form = new FormState({
            name: this.name,
            groupname: this.groupname,
            description: this.description,
            timeSyncType: this.timeSyncType,
            timeValue: this.timeValue,
            tTimeUnit: this.timeUnit,
            fixedStartTime: this.fixedStartTime,
            fixedEndTime: this.fixedEndTime,
        });
    }
   
    onSubmit = async (event) => {
        alert('gh');
        event.preventDefault();
        //  Validate all fields
        const res = await this.form.validate();
        // If any errors you would know
        if (res.hasError) {
        // tslint:disable-next-line:no-console
        console.log(this.form.error);
        return;
        }
        return true;

    }
}

interface Props {
}

interface State {}

interface InjectedProps extends Props {
  dashboardStore: DashboardStore;
}

@inject('dashboardStore')
@observer
class DashboardConfig extends React.Component<Props, State> {
    static contextTypes = {
        router: PropTypes.object
    };

    dashboardStore: DashboardStore;
    configForm: DemoState;

    constructor(props: Props) {
        super(props);
        this.dashboardStore = this.injected.dashboardStore;
        this.configForm = new DemoState(this.dashboardStore.currentDashboard);
        this.removeDashbaord = this.removeDashbaord.bind(this);
        
        this.saveConfig = this.saveConfig.bind(this);
    }

    get injected() {
        return this.props as InjectedProps;
    }

    // tslint:disable-next-line:no-any
    saveConfig(event: any) { 
        let status = this.configForm.onSubmit(event);
        if (status) {
            alert(JSON.stringify(this.configForm.form));
        }
    }

    }

    render() {
        const data = this.configForm;
        return (
        <div id="dashboardConfig" className="col-sm-12">
        <h5>Configure</h5>
            <Form >
                <FormGroup row={true}>
                    <Col sm={12}>
                        <label>Name</label>
                        <Input
                            onChange={(e) => data.name.onChange(e.target.value)}
                            value={data.name.value}
                            type="text"
                            placeholder="Dashbaord Name"
                        />
                    </Col>
                </FormGroup>
                <FormGroup row={true}>
                    <Col sm={12}>
                        <label>Description</label>
                        <Input
                            type="textarea"
                            onChange={(e) => data.description.onChange(e.target.value)}
                            value={data.description.value}
                        />
                    </Col>
                </FormGroup>
                
                <FormGroup check={true} row={true}>
                    <Col sm={{ size: 10, offset: 2 }}>
                        <Button type="buttom" onClick={this.saveConfig}>Submit</Button>
                    </Col>
                </FormGroup>
            </Form>
            
        </div>
        );
    }
}

export default DashboardConfig;

A way to add server error to form onSubmit

class DemoState {
// Create a field
username = new FieldState('').validators((val) => !val && 'username required');

// Compose fields into a form
form = new FormState({
username: this.username
});

onSubmit = async () => {
// Validate all fields
const res = await this.form.validate();
// If any errors you would know
if (res.hasError) {
console.log(this.form.error);
return;
}
// Yay .. all good. Do what you want with it
submitToServer()
.then(() => console.log('Yay success'))
.catch(error => this.form.setError(error.message)); // So we can display some error message (e.g. username password pair invalid, etc.)
};
}

Reconsider `onHotChange` / `hotValue`

I feel calling it hot does prevent bugs. But a few reasons come to mind to just call it value/onChange

  • Explaining how to create a Field is intuitive
  • Matches existing framework concepts (hot was something we did just to help people that failed to read docs)
  • value is verbose e.g. form.value.subForm.value.subField.value. A suggested alterantive for the validated value is just $ e.g. form.$.subForm.$.subField.$. Not sure this is better.
  • Angular 1 ngModel had value and validated one was $valid

Thinking this through 🌹

disable particular fieldstate validator

Hello @basarat,

I see we can disable autovalidation on a fieldstate
but it is possible disable or enable validation on a fieldstate

example:

I have a radiobutton with two inputs and each inputs have a required validation.

If I choose the first input, I want disable the second input validation and this using the same validation method for the 2 inputs.

or in the FormState validators disable a validation.

the disableAutoValidation works on fieldstate but when I validate form they are réactivate.

any solutions for this

If you can add a example I take

thanks

Allow hook for change to error state

I've got this form where two elements depend on each other. There is a 'site' selector, and depending on what comes out of that selector the Date(s) selector blanks out certain dates that are invalid for that site.

I'm trying to get this hooked up using the hooks of the field, but don't get very far. The below .gif shows.
The onUpdate triggers way too often for me to be usable (it would involve requests to the API).

The validation debounce is also perfect for this (someone clicking quickly through this should not trigger the requests), but the on$ChangeAfterValidation apparently does not trigger after invalidating.

I got three ideas:

  • Add parameter to on$ChangeAfterValidation indicating status (hasError: boolean). The name seems to allow both error and non error states (AfterValidation doesn't indicate success/failure). This would be the most true to the meaning + allow better sustainability with upgrades (see next option).
  • Trigger on$ChangeAfterValidation on failures too. This would probably be the easiest, but people depending on the library might be surprised if they already attach to this hook and it triggers more, potentially breaking a lot of integrations.
  • Separate on$ChangeAfterError. This speaks for itself. Could also be added while also doing one of above, with another added hook on$ChangeAfterValid

Let me know your thoughts, would be happy to implement

TypeScript type issue with FieldState

When i was trying to use formstate with a taginput i quickly ran into a problem. I was getting a type error when i was setting the default value of the FieldState as following: new FieldState([]).

I get the following error message form typescript:
TS2345:Argument of type 'string[]' is not assignable to parameter of type 'never[] | undefined'. Type 'string[]' is not assignable to type 'never[]'. Type 'string' is not assignable to type 'never'.

I had to set the default value to new FieldState(['']) in order to let the typesystem work. Is there something i can do to work arround this :-) ?

How to get a object with values only?

Hey, first of all this is amazing package comprared to many other over complicated implementations :-)

I would like to have a method which would deliver a object with values only.

That would mean that i shouldn't have to destructure the fields everytime i have to post it to a server :-)

I think this would be a great improvement :-)

Expose a property notifying if field has been validated

I want to keep my fields 'default' gray until they have been acted upon (data entered), then go either into error mode (red) or valid mode(green).
I currently do this using the 'hack' (this.props.value as any).lastValidationRequest > 0.
I'd like to make this a first class concept. The validators for, for example, a required value, rightfully only kick in after adding 1 character and removing, the library should expose this concept of having validated or being in an unknown/unvalidated state.

I propose on the FieldState class:

@computed public get hasBeenValidated() : boolean {
    return this.lastValidationRequest > 0;
}

Let me know if you'd like this, happy to make a pull request! If there's a more supported way per-field to allow this to happen I'd be happy to use that of course :)

Double validation when validating on blur and onsubmit

I have validation onBlur on each input field. And on submit I call validate on the formstate containing all the fields.

When calling validate on the formstate, already validated fields are validated again. Is there any way to avoid this?

Regarding error messages

I was thinking, it might be nice if the error messages themselves are left out of the design. Instead, every validator would have a name, and the view could handle doing the error message display, e.g.

fieldState.errors('errorName') ? <p> error message here</p> : null

This would make it easier to add dynamic content (like help etc) to errors, as well as to do localisation / translation.

The validator becomes a boolean function f(x) -> true if valid, false if not

How do you set validation errors returned when trying to submit the form?

I've had a look through the documentation and the API, and I can't see a way to set form level validation errors which have been returned when submitting the form.

e.g.
Take a simple Login form...
When the form is submitted it's content is sent to the server, the server checks if the credentials are correct, and then returns either saying "success" or "Login failed".
If the login failed then that would often be displayed as a "form level" validation error.

It seems that this is possible at the field level, as (if I'm reading the source right) you can directly set the error observable field.

e.g.
A Register User form...
The Username field would have an async validator on it that checks if the username is available.
But the async validator doesn't reserve the username, it simply checks the availability at that moment in time.
When the user submits the form the server might reply with a validation failure as someone else has taken that username between the async validator running and the form actually being submitted.
And the client side code sets usernameFIeldState.error = "This username has already been taken."

Is doing this at the form level possible?

Or is it something I should be handling specially outside of form-state?

Serialization of FormState

What about serializing a nested FormState?

It would be nice if one could easily integrate some external library as serializr. I tried with no success but maybe you have already thought a different way of doing this.

Touch upon server-side validation in documentation

Hey there!

Love formstate ❤️

Upon reading documentation n-th and thinking/planning how to do serverside validation for auth forms, I though that it would be a good idea to add server-side validation suggestions/guidelines to formstate documentation. I do understand that it's not something that should be explicitly handled by formstate, but since, for example, login form is a such'a common form and final validation happens on the serverside it would be very nice to have an example in documentation of how you would handle it with formstate.

Way to get plain values of FormState?

Seems that there is no method such as getValues() to just get plain values of the form, without need to do something like that:

const form = new FormState({
  a: new FieldState(""),
  b: new FieldState("")
})

function doSumbit() {
  const submitValue = form.getValue() // not possible, should do this:
  const submitValue = {
    a: form.$.a.$,
    b: form.$.b.$,
  }
}

This could become too deep with nested forms :)

P.S. Thank you for library!

mobX.toJS does not work as expected with FieldState

When using mobX.toJS to serialize the object, I expect that FieldState will be resolved to the field value
Example:

Declaration

firstname = new FieldState({
    value: 'John',
    validators:[(val) => !val && 'Required']
});

after mobX.toJS

firstname: {
    value:'John',
    $:'John'
    ...
    ...

expected

firstname:'John'

Arrays

Hi, Basarat, great work as usual ;) Already using it in my project. I have one question though about array. How do you specify a field that contains an array of complex objects. In your docs you have two form states, is that necessary?

TL;DR;

So, can a FieldState contain an array of FormStates as its value?

Example:

class A {
  a: number;
  b: string;
}

class B = {
  arr: A[]
}

can I model it like this?

// i like to have all types globally available in order to reduce imports, don't judge ;)
declare global {
  namespace App.Models {
    type AStateFields = {
      a: FieldState<number>;
      b:  FieldState<string>;
    }

    type BStateFields = {
      arr: FieldState<AStateModel[]>;
    }

    type AState = AStateModel;
    type BState = BStateModel;
  }
}

class AStateModel extends FormState<App.Models.AStateFields> {
  constructor(a: A) {
    super({
      a: new FieldState(a.a),
      b: new FieldState(a.b),
    })
  }
}

class BStateModel extends FormState<App.Models.BStateFields> {
  constructor(b: B) {
    super({
      arr: new FieldState(b.map(item => new AStateModel(item)))
    })
  }
}

Breaking change to 1.0.0

Breaking change in the last version:
error TS2551: Property 'reinitValue' does not exist on type 'FieldState<number>'. Did you mean '_initValue'?

It would be goodl to have a CHANGELOG or release notes where changes are visible.

Cross-field validation not working

I often need validation rules that access more than just a single field. Typical examples are:

  • 'Password' and 'Repeat password': The validation rule on 'Repeat password' should not only check that the field is not empty. It should also check that the value is identical to the value in 'Password'.
  • 'Start time' and 'End time': The validation rule on 'End time' should not only check that 'End time' is a valid time. It should also check that 'End time' is greater than 'Start time'.

The important thing here is that these validators should not just be re-evaluated when the value of their own field changes. They also have to be re-evaluated when the value of another field that's used in the validator changes. For example, the validator on 'Repeat password' should run whenever 'Password' or 'Repeat password' is changed.

Luckily, MobX is great at tracking these kinds of dependencies, and it's my understanding that you use MobX internally. So I expected that I could simply access the values of other fields within a validator. MobX would then automatically detect that the validator for 'Repeat password' depends on the value of 'Password' and re-evaluate it accordingly.

For some reason, this doesn't seem to work. The validator on 'Repeat password' only gets re-evaluated when I change the value of 'Repeat password', not when I change the value of 'Password'.

Below is my demo code. I'm sure I must have made some small error, but I can't find it.

import React, { Component } from 'react';
import { FormState, FieldState } from 'formstate';
import { observer } from 'mobx-react';

@observer
class Input extends Component {
  render() {
    const { fieldState, label } = this.props;
    return (
      <div>
        <p>{label}</p>
        <p style={{ color: 'orange' }}>{fieldState.error}</p>
        <input
          type="text"
          value={fieldState.value}
          onChange={args => fieldState.onChange(args.target.value)}
        />
      </div>
    );
  }
}

@observer
export default class Usecase extends Component {
  constructor(props) {
    super(props);
    this.formState = new FormState({
      userName: new FieldState({ value: '' })
        .validators(value => !value && 'User name is required.'),
      password: new FieldState({ value: '' })
        .validators(value => !value && 'Password is required.'),
      repeatPassword: new FieldState({ value: '' })
        .validators(value => {
          if (!value) return 'Repeated password is required.';
          return (value !== this.formState.$.password.$) && 'Repeated password must match password.';
        })
    });
  }

  componentWillMount() {
    this.formState.validate();
  }

  render(){
    return (
      <form>
        <Input fieldState={this.formState.$.userName} label="User name" />
        <Input fieldState={this.formState.$.password} label="Password" />
        <Input fieldState={this.formState.$.repeatPassword} label="Repeat password" />
      </form>
    );
  }
}

FieldState constructor allow only value

Given that the FieldState constructor requires a config object that only has one obligatory value (the TValue) it would be nice sugar if something like the following would be the constructor syntax'es. This example uses a FieldState for simplicity:

(did not test below code so not 100% sure if that would be the best way, possibly FieldStateConfig should just be an interface?)

class FieldStateConfig {
        value: TValue;
        onUpdate?: (state: FieldState<TValue>) => any;
        autoValidationEnabled?: boolean;
        autoValidationDebounceMs?: number;
}
constructor(config: FieldStateConfig);
constructor(value?: any) {
    if(value ! instanceOf FieldStateConfig) {
        value = new FieldStateConfig(value);
    } 
    //execute default logic    
}

If I'm correct, this should be callable by:

new FieldState<boolean>(true);

and

new FieldState<boolean>({
    value: true,
    autoValidationDebounceMs: 250,
    //etc
});

[Question] Is there an easier way to populate fields with dynamic data?

Hi,

I have a question about the best way to poptulate fields with dynamic data.

Is the code below the best approach to populate fields with dynamic data :-) ?

export interface IActionDialogEditFormData {
    name: string;
    sorting: number;
    description: string;
    heading: boolean;
}

// Forms
class TagEditForm {
    
    // Create a fields
    name = new FieldState('').disableAutoValidation().validators(required, between(5, 100));
    sorting = new FieldState(1000).disableAutoValidation().validators(required);
    description = new FieldState('').disableAutoValidation();
    heading = new FieldState(false);

    // Compose fields into a form
    state = new FormState({
        name: this.name,
        sorting: this.sorting,
        description: this.description,
        heading: this.heading
    });

    constructor(tag: IActionDialogEditFormData) {
        this.name.onChange(tag.name);
        this.sorting.onChange(tag.sorting);
        this.description.onChange(tag.description);
        this.heading.onChange(tag.heading);
    }

    getValues() {
        return {
            name: this.name.$,
            sorting: this.sorting.$,
            description: this.description.$,
            heading: this.heading.$
        };
    }

}
```

async default value

Hello,

Your component is great, but can you an in the demo an example with async value after get values in ajax call in componentDidMount ?
I mean when I want reset the form, I would like retrieve the initial value from my data server object
and no a empty field.

Thanks for the answer

[Feature] check if FormState has changed recursively

A common scenario when working with forms is to show a confirmation prompt when user edited some data and want to leave the form page without saving it.
Example from react-router: https://reacttraining.com/react-router/web/example/preventing-transitions

I wanted to implement it using formstate but I didn't found any public API that could help me with this task.

I have figured out that I could parse formstate data structure (recursively) on the initial load to create a snapshot by serializing data so when later onLeave event handler is invoked I could compare it with the current structure to decide if the form was changed or not.

I wanted to ask if maybe this is currently possible to do with public or internal API?
If not perhaps it is something worth adding to the library?

The ideal and generic solution would be to bake in serialize functionality to the formstate object that would return serializable object literal with values extracted from value props of formstate fields.

This would cover scenario with empty inital state (aka pristine) and also scenarios with repopulated forms with some existing data.

Alternatives to wrapping fields

In a discussion on how to support non-string datatypes the recommendation was made, as elsewhere in the docs, to wrap underlying fields into new fields that are formstate specific. ( #51)

After some experience with forms we've become wary of wrapping input components into new ones. There's a tension between reusability and readability/customizability here. For the latter a flat form that uses the underlying form components is often nicer. (especially if validation error display is also wrapped, though that's of course not necessary -- Ant Design separates that into a Form.Item component). We obscure the documentation of form components by wrapping. For instance, we use Ant Design, and if we wrap everything directly, we cannot just refer developers to the Ant Design documentation anymore, but instead have to make people understand the new layer we've created on top of it.

We've therefore gone for another approach where we stick to the UI component library's UI components as much as possible. This example is antd specific but I'm trying to communicate the idea:

function validationProps(val) {
  return {
    validateStatus: val.hasError ? "error" : undefined,
    help: val.error
  };
}

function inputProps(val) {
  return {
    value: val.value,
    onChange: e => val.onChange(e.target.value)
  };
}

and we use these as follows with antd, where Form.Item and Input are direct imports from antd:

          <Form.Item
            {...formItemLayout}
            hasFeedback
            {...validationProps(username)}
          >
            <Input type="text" {...inputProps(username)} />
          </Form.Item>

This works well so far.

For fields that work with different data types like discussed in #51 , we can still create a higher-level wrapping and since we move outside of what antd offers, that's probably all right, though I want to explore whether perhaps something can be done with a clever inputProps instead.

A few questions:

  • What is your opinion of this approach? perhaps you've seen problems with it that we haven't encountered yet.

  • Would it make sense to expand the documentation to reference this as an alternative? I could try to write something up.

  • Are there hooks we can discuss that would help support this API? the functions work, but of course I'd like to be able to write username.validationProps() instead and save an import. We can't hardcode the antd behavior though, and I know you're wary of expanding the API too far. Could be accomplished by subclassing FieldState and using the subclass instead in form definitions -- are there any problems with that approach?

Question: API for setting form state

I'm excited to try formstate. I like the API. I apologize if this is in the docs and I've missed it, but is there a way to set form state via an object? For editing as opposed to creating an entity, can you pass in the object that has the entity's current values?

Infinite loop while validating

I managed to narrow it down somewhat: A nested form with compose() on the outer (with validators or not) with at least two onChange will trigger an infinite loop.

Allow debounce timeout default to be set to a different value

Similar to for example mobx's useStrict(true), I'd love to be able to set the debounce timeout as a default to a different value than the current 200.

For most of my fields a different value (25) works smoother, except in certain cases where there are API calls etc and the debounce is beneficial when higher.

Adding this to each FieldState feels like a bit of unnecessary boilerplate.

Setting it globally would of course mean introducing some kind of global formstate singleton, which though offering this as an option might be inadvisable.

As an other option, it could possibly be added to the FormState constructor which would subsequently set it on each child FieldState except if different than the default. Still a bit of boilerplate but possible a better option given that no global state is created for this.

For big forms (I got one with a shocking 29 fields) this would greatly reduce the boilerplate.

do not validate invalid values until they become valid

I have a minLength-validator, which simply checks on the length of a string.

newPassword: new FieldState<string>('').validators(
        minLength(7, 'Password must be 7 chars or longer')
)

When changing the form-field with field.onChange(value) it immediately validates and the form is invalid.
Is there a way to tell FieldState to not validate until it once was valid?

Non async validate

Hi, you specify that return type of validate is Promise<{ hasError: true } | { hasError: false, value: TValue }>. This looks like it is possible to do sync validation. Yet, I cannot make it work as I always have to call it with await.

I could really use sync version as it's much easier to test as well. Am I missing something? Thanks!

Add event handlers through chaining (much like validators)

I propose the following API addition (one for each handler, see #14 for handler additions):

onChangeAfterValidation: (...handlers: (() => void)[]) => this;

This would allow the following configuration:

public repeated= new FieldState<boolean>({
        value: false
    })
    .validators(val => val !== false && '')
    .onChangeAfterValidation(() => {console.debug('handler1')}, () =>{console.debug('handler2'});

Documentation

Hi guys,

Where is the documentation available? I'm not being able to get it.

Thanks

Cross field validation in onChange of a field

There is currently no way to register a validator in a field, that needs to read the value from another field, is there?

I like your validation concept very much and we are using mobx and mobx-react in our project, so your validation framework could fit very well. But one of our reasons why we are looking for a framework anyway, is to easily define cross field validators which should be triggered on changing a specific input field.

Will there be an other method to trigger those validators, besides manually calling the validate function of the surrounding form? Or is there any smart way to structure the FormStates and FieldStates to achieve this? Or is there already a method how validators can read values from other fields?

raw value versus converted value

Another philosophical comment in relation to #51

You wrote an example there:

0175a65

Here you convert to null in case conversion cannot take place. The validator then needs to respond to the null case and handle it appropriately. To display the value in the input component, you convert null back to the empty string. This means that when you type non-string input, you see no input appear on the screen.

But what if I type "a123"? It's not an unreasonable UX to expect a validation error and then the ability to correct to 123, but I can't I won't be allowed to type at all.

To remedy this, NumberInputField could track of its raw input in an observable prop, and always render this into value. If still passes along null if it cannot be converted to a string. And on initialization of the NumberInputField, you'd need to initialize this raw value prop from fieldState. But what if fieldstate is changed in the mean time by the program? How do we update the raw value in the NumberInputField? We don't always want to update the raw value because in that case the act of typing would modify fieldState and we'd lose information.

Am I making things too complicated or is this a real usability issue with this approach? If this is a real issue, I think the framework should offer support for it, at the very least in the form of documentation.

Problem with typescript and `onChange`

Currently, the type for onChange looks like this: onChange: (value: TValue) => void;
Where TValue is the type I provided to FieldState e.g. public myField: FieldState<number>;

Now, passing other values than number to onChange will result in typescript complaining.
Shouldn't onChange accept any-type and leave it up to the validator?
In any case I want my form-element to accept the value and validate it with a proper error-message.

how does formstate handle non-string data?

I haven't been able to find documentation guidance on this, so I'm asking here:

Let's say I have a field that maintains a number (I'm a typescript noob, but TValue would be of type number for the FieldState), but the displayed form value is text. Where do I plug in a renderer that turns the number into text for display purposes, and converts the text back into a number? I'd imagine such a converter to run before validation takes place, so I can treat the value as a number in there; the conversion failing is another sort of validation error.

Is the idea that in React code I also convert the number to a string before I pass it to the input value, and write a custom onChange that does the conversion to a string before I pass it to formstate? I.e. something like:

<input value={render(foo.value)} onChange={e => foo.onChange(convert(e.target.value))} />

'convert' would return a special sentinel (or just null) in case the conversion fails, so that the validators can make use of this information and give appropriate output.

I'm looking for a way to define this back and forth translation as part of FieldState instead. I guess a TFormValue that's the value that's actually shown in the form as opposed to TValue that's the value that's maintained immediately.

Computed Field

I have a store like this:

class Order {
  @observable price
  @observable quant
  @computed get total() {
    return this.price * this.quant
  }
}

How can I create a form where total is updated automatically, like this:

...
render(){
    return (
      const data = this.data;
      <form onSubmit={data.onSubmit}>
        <input
          value={data.price.value}
          onChange={(e)=>data.price.onChange(e.target.value)}
        />
        <input
          value={data.quantt.value}
          onChange={(e)=>data.quant.onChange(e.target.value)}
        />
        <input
          value={data.total.value}
          readonly={true}
        />
      </form>
    ));
...

demo.html

  • Demo creating a simple Input
  • Demo creating a simple Field
  • Demo creating a simple InputField

Setup toolchain to auto build and deploy to formstate.github.io/demo 🌹

  • TODO: add debounce function to validation.
  • TODO: document debounce function to validation.
  • TODO: consider validation.ifValue as a part of core.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.