When validating or preprocessing input values, we often write multiple if statements like this:
if (value.includes(' ')) {
value = value.replaceAll(' ', '')
}
if (value.length > 10) {
value = value.slice(0, 10)
}
However, as the number of conditions grows, readability drops and reusability suffers.
By switching to a functional approach, the logic becomes much cleaner and composable.
Here’s an example using React Hook Form’s Controller for an Input component.
It accepts multiple filter functions from the outside and applies them sequentially to the input value:
import { Input } from 'antd';
import { Controller, FieldValues } from 'react-hook-form';
type RHInputType<T extends FieldValues> = {
name: string;
filters?: ((value: string) => string)[];
// ... other props
};
function RHInput<T extends FieldValues>({
name,
filters = [(value) => value],
...props
}: RHInputType<T>) {
return (
<Controller
name={name}
render={({ field: { onChange, ...field } }) => (
<Input
{...field}
{...props}
onChange={(e) => {
const newValue = filters?.reduce(
(result, filter) => filter(result),
e.target.value
);
onChange(newValue);
}}
/>
)}
/>
);
}
const removeSpaces = (value: string) => value.replace(/\s+/g, '')
const limitLength = (value: string) => value.slice(0, 10)
<RHInput
...
filters={[removeSpaces, limitLength]}
/>
Now, whenever the input changes, the value is processed in order: removeSpaces → limitLength, and the final value is passed to onChange.
filters array has a default of [(value) => value], so if no filters are provided, nothing happens.reduce, determining the order by the array index.if statements, this pattern improves reusability, flexibility, and adheres to functional programming principles.While a single filter may be simpler with an if statement, the pipeline pattern ensures scalability for more complex logic.
The key takeaway: by typing filters as an array of functions and providing a default identity function, you can control the filter sequence externally without affecting the component if no filters are applied.