Part 4: simplr-forms — declarative forms for React . Normalizers and Modifiers

This is series of posts documenting the development of simplr-forms library.

Originally posted on May 12th.

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 Normalizersbehaviour.

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.

Next in series

Part 4.X Status report

One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.