Transform inputs

You've probably entered your phone number or bank details into a form and, like magic, it was converted into a more readable format. Learn how that works with Modular Forms in this guide.

Input transformation works only if your fields are controlled. Make sure you have read the guide about controlled fields beforehand.

The transform prop

The goal of input transformation is to change the user's input at a given time. Typically at the input or change event. This is made possible with the transform property of the Field component.

As with the validate property, you can pass a single function or multiple functions in an array. You can use our toCustom function or write your own functions based on it. More on that in a moment.

<Field
  name="phone"
  transform={toCustom(
    (value, event) => {
      // Transform phone number here
    },
    { on: 'input' }
  )}
>
  {(field, props) => (
    <input
      {...props}
      type="tel"
      placeholder="(000) 000-0000"
      value={field.value}
    />
  )}
</Field>

How toCustom works

The first parameter of the toCustom function is executed if the current event type is the event type you specified in the second parameter. The function gives you access to the current value, as well as to the event object, which you can use to access the reference of the <input /> element via event.currentTarget if needed.

String transformation

Besides toCustom, Modular Forms also provide toTrimmed, toLowerCase and toUpperCase by default to transform strings. If you have ideas for other useful transformation functions, let us know with an issue on GitHub.

Create custom functions

To make your code reusable and more readable, you can create your own transformation functions based on toCustom. As promised, we show how this works, using a phone number transformation as an example.

Phone number transformation

A US phone number starts with 3 numbers, surrounded by round brackets, which gives information about the area. Then, after a space, 7 more numbers follow, with a hyphen between the first three and the last four.

import { toCustom, TransformOptions } from '@modular-forms/solid';

export function toPhoneNumber(options: TransformOptions) {
  return toCustom<string>((value) => {
    // Remove everything that is not a number
    const numbers = value.replace(/\D/g, '');

    // Continue if string is not empty
    if (numbers) {
      // Extract area, first 3 and last 4
      const [, area, first3, last4] = numbers.match(
        /(\d{0,3})(\d{0,3})(\d{0,4})/
      );

      // If length or first 3 is less than 1
      if (first3.length < 1) {
        return `(${area}`;
      }

      // If length or last 4 is less than 1
      if (last4.length < 1) {
        return `(${area}) ${first3}`;
      }

      // Otherwise return full US number
      return `(${area}) ${first3}-${last4}`;
    }

    // Otherwise return an emty string
    return '';
  }, options);
}

Use custom function in JSX

You can now call the toPhoneNumber transformation function and pass it to the transform property as follows:

<Field name="phone" transform={toPhoneNumber({ on: 'input' })}>
  {(field, props) => (
    <input
      {...props}
      type="tel"
      placeholder="(000) 000-0000"
      value={field.value}
    />
  )}
</Field>

Advanced usage

Together with controlled fields, the transformation API can also be used to hold values internally in a different format than they are externally accessible. A practical example is the handling of monetary amounts.

Monetary amounts

In many cases, monetary amounts are stored in the database as integers and thus in cents. However, as an end user, we expect a decimal number in € or $. To save the effort of converting the numbers to € or $ after retrieving them from the database and converting them back to cents before saving them, the following trick can be used.

Note that this procedure only works if JavaScript is enabled in the browser.

Practical example

Pass the amounts to Modular Forms in cents. Then, before you display them in an <input /> element, convert the amount to € or $. Once an input is made, use the transform property to convert the amount back to cents before Modular Forms updates the state. Below is a code example.

<Field
  name="price"
  type="number"
  transform={toCustom((value) => value && value * 100, {
    on: 'input',
  })}
>
  {(field, props) => {
    const getValue = createMemo<number | undefined>((prevValue) =>
      !Number.isNaN(field.value) ? field.value && field.value / 100 : prevValue
    );
    return <input {...props} type="number" value={getValue()} />;
  }}
</Field>

Make it reusable

If you need this functionality for multiple fields, we recommend wrapping the logic in a PriceInput component and toCents transformation function.

import {
  Maybe,
  MaybeValue,
  toCustom,
  TransformField,
} from '@modular-forms/solid';

export function toCents<
  TValue extends MaybeValue<number>
>(): TransformField<TValue> {
  return toCustom<TValue>(
    (value) => value && ((value * 100) as Maybe<TValue>),
    { on: 'input' }
  );
}

Use input masks

Another way to transform inputs are input masks and to our advantage the SolidJS community provides a suitable package for this with @solid-primitives/input-mask. In the following I would like to make an example.

Create an input mask

To create an input mask you use the createInputMask primitive. As generic you pass our FieldEvent and as parameter for example a string that defines the mask. Then you can use the mask in the toCustom function as follows:

import { createForm, FieldEvent, toCustom } from '@modular-forms/solid';
import { createInputMask } from '@solid-primitives/input-mask';

type DateForm = { date: string };

export default function App() {
  const [dateForm, { Form, Field }] = createForm<DateForm>();
  const dateMask = createInputMask<FieldEvent>('99/99/9999');

  return (
    <Form>
      <Field
        name="date"
        transform={toCustom((_, event) => dateMask(event), { on: 'change' })}
      >
        {(field, props) => (
          <input
            {...props}
            type="text"
            placeholder="01/01/2023"
            value={field.value}
          />
        )}
      </Field>
      <button type="submit">Submit</button>ubmit" />
    </Form>
  );
}

Custom toInputMask function

To make the code more readable you can create your own toInputMask function.

import { FieldEvent, toCustom, TransformOptions } from '@modular-forms/solid';

export function toInputMask(
  inputMask: (event: FieldEvent) => string,
  options: TransformOptions
) {
  return toCustom((_, event) => inputMask(event), options);
}

Then you can use toInputMask in your JSX code as follows:

<Field name="date" transform={toInputMask(dateMask, { on: 'change' })}>
  {(field, props) => (
    <input
      {...props}
      type="text"
      placeholder="01/01/2023"
      value={field.value}
    />
  )}
</Field>