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>