Part 4: simplr-forms — declarative forms for React . Normalizers and Modifiers
This is series of posts documenting the development of simplr-forms library. All posts can be found in GitHub.
- Part 1: Why are we doing this?
- Part 2: Core, validation, testing.
- Part 3: First e2e flow, FormStore.ToObject()
- Part 4: Normalizers and Modifiers
- Part 4.X: Status report
April 27th notes.
Attendees
What’s new?
Modifiers
and Normalizers
work properly with fields!
Soooo… What are those?
Let’s start with Normalizers
. If you ever needed to make sure your input is all lower-case, upper-case or alphanumeric, think how would you approach that scenario.
Now look at this. I introduce to you: Normalizers
.
Imagine you need a form for a promo code. It has a few limitations: it’s upper-case and alphanumeric characters only.
You could let user enter whatever input and then take what’s needed, stripping all spaces and symbols away.
But that is not user friendly, because user cannot see what’s happening behind the scenes and their imput is being modifier without any visual feedback.
You could also take onChange
event and enforce required rules there, setting the value to the filtered one after each key stroke. But the logic gets messy and it's an imperative approach to solve this problem.
What if you could take this familiar form application (yep, that’s the whole application, if you didn’t read the last post):
import * as React from "react";
import * as ReactDOM from "react-dom";
import "tslib";
import { FormStore } from "simplr-forms-core/stores";
import { Form, Text, Submit } from "simplr-forms-dom";export class Main extends React.Component<{}, { formId: string }> {
protected onSubmit: any = (event: React.FormEvent<HTMLFormElement>, store: FormStore) => {
console.log("Submitting...");
console.log(store.ToObject());
}; render() {
return <Form onSubmit={this.onSubmit}>
<label>
Promo code:
<Text name="PromoCode" />
</label>
<Submit>Submit</Submit>
</Form>;
}
}ReactDOM.render(<Main />, document.getElementById("react-root"));
And declare required rules well… In a declarative fashion?
E.g. for upper-case, you import an UpperCaseNormalizer
like this:
import { UpperCaseNormalizer } from "simplr-forms-core/normalizers";
And declare the rule right inside the field like this:
<Form>
<label>
Promo code:
<Text name="PromoCode">
<UpperCaseNormalizer />
</Text>
</label>
<Submit>Submit</Submit>
</Form>
Convenient, right? Now you think that the UpperCaseNormalizer
is some complex peace of code, right?
Well, decide for yourself, because the whole UpperCaseNormalizer
looks like this:
import { BaseNormalizer } from "simplr-forms-core/normalizers";
import { FieldValue } from "simplr-forms-core/contracts";
import { ValueOfType } from "simplr-forms-core/utils";export class UpperCaseNormalizer extends BaseNormalizer<{}, {}> {
Normalize(value: FieldValue): FieldValue {
if (ValueOfType<string>(value, UpperCaseNormalizer.name, "string")) {
return value.toUpperCase();
}
}
}
Library imports are organized and easily reachable.
And what is ValueOfType
thingy there?
That’s just a convenient helper function that checks if typeof value === "string"
and throws an error with a proper message if it's not. Take it or leave it. The library is not opinionated about that.
There’s more.
We only have our value normalized to upper-case now. What about alphanumeric part?
Well, you can compose Normalizers
and even decide the order of them as you wish, all still declaratively, like this:
<Form>
<label>
Promo code:
<Text name="PromoCode">
<UpperCaseNormalizer />
<AlphanumericNormalizer />
</Text>
</label>
<Submit>Submit</Submit>
</Form>
Now value will be upper-cased and all non-alphanumeric symbols filtered out. If you think that upper-casing should go after alphanumeric filter, you just change the order, still declaratively:
<Form>
<label>
Promo code:
<Text name="PromoCode">
<AlphanumericNormalizer />
<UpperCaseNormalizer />
</Text>
</label>
<Submit>Submit</Submit>
</Form>
Want something more?
Normalizers
are regular react components, therefore, they can take props. E.g.:
<AlphanumericNormalizer allowSpaces={true} />
Now you can unleash the power of declarative rule listing and composition and use props for an easy control of Normalizers
behaviour.
When user is typing discount123
, this is how it looks in the field (also, in the store):
Normalizers
enforced all declared rules.
This is how you easily organize and reuse your normalization logic.
Amazing! What about Modifiers
?
Modifiers
are another convenient feature. You use them just as you used Normalizers
: declare inside the field.
But what they do is a bit different: Modifiers
format and parse value of the field.
To be more specific:
- When value comes from
FormStore
to the field, it is formatted. - When it changes, before going to
FormStore
, it is parsed.
This enables you to have a numeric
value in the store and a string
one in the field. Or storing Date
in the store and formatting it for user in a human-readable format.
And Normalizers
get an already parsed value, so you don't have to care about converting it to the right type in the Normalizers
and it's the only thing you do in Modifier
. Separation of concerns 👍
Example of Modifier
usage:
<Form onSubmit={this.onSubmit}>
<label>
Full name:
<Text name="FullName">
<NumericModifier />
</Text>
</label>
<Submit>Submit</Submit>
</Form>
And you can also use both Modifiers
and Normalizers
in the same Field
:
<Form onSubmit={this.onSubmit}>
<label>
Full name:
<Text name="FullName">
<NumericModifier />
<UpperCaseNormalizer />
</Text>
</label>
<Submit>Submit</Submit>
</Form>
Even if this case does not make much sense (numeric->upper-case), I’m sure someone will find a proper usage.
What’s next?
We’ll look at Reset
and Clear
buttons, how they work and what are they used for. We'll learn how initialValue
and defaultValue
can be used in a meaningful way.