Let's say you have a relatively simple component. It renders HTML input and handles user submission.
type SimpleInputProps = {
handleChange: Function
}
const handleChange = (value: string): void {
pushToBackend(changeValue)
}
const SimpleInput = (props: SimpleInputProps): JSX.Element => {
const { handleChange } = props
return (
<input type="text" onChange={handleChange} />
)
}
You want to validate user input. Naturally, you don't want to hard-code the validation logic inside the component. You want to encapsulate it and use it through React composition. Eventually you need to get something like this:
const handleChange = ...
const SimpleInput = ...
<ValidationWrapper validations={validationList}>
<SimpleInput handleChange={handleChange} />
</ValidationWrapper>
I must say, I don't want to use libraries for forms, because they are too heavy for my tasks right now.
So, we need to implement the ValidationWrapper
component that encapsulates the validation logic.
As you can see, we want to pass validation handlers as ValidationWrapper
properties.
The wrapper should take these handlers and decide to execute our handleChange
function or throw error messages.
So how can we achieve this? Something like that:
type ValidationWrapperProps = {
children: JSX.Element
validations: Function[]
}
const ValidationWrapper = (props: ValidationWrapperProps): JSX.Element => {
const { validations, children } = props
// component must have a handler in props
const originalHandler = children.props.handleChange
const { errorMessages, patchedHandler } = useValidation(
originalHandler, validations,
)
return (
<>
<children.type {...children.props} handleChange={patchedHandler} />
{errorsMessages}
</>
)
}
What's going on here? We just put our input component in a validation wrapper and patch its handler with the useValidation
hook. Yes, all magic lives on the hook. But it is already clear that this approach looks pretty compact. Let's take a look at the implementation of useValidation
.
Actually, it can be anything. The main idea is to put the validation logic in one place.
I'll show the simplest example:
type ValidationHookProps = {
callback: Function
validations: Function[]
}
type ErrorMessages = string[]
const useValidation = (props: ValidationHookProps): ErrorMessages => {
const { callback, validations } = props
const [errorMessages, setErrorMessages] = React.useState<ErrorMessages>([])
const patchedHandler = (changeValue: any): void => {
const errors = validations.map((validate: Function) => validate(changeValue))
if (!errors.length) return callback(changeValue)
setErrorMessages(errors)
}
return { errorMessages, patchedHandler }
}
It's also pretty simple here. The hook creates a state to store error messages
that we grab from the validation function.
We also create a handleAction
function. It calls validation handlers and receives messages from them. If we have errors, it won't call the original handler.
The useValidation
hook returns a patched handler for validating user input and a list of error messages that you can use to display the error to your users.
Thus, we have achieved posibility to check user input through react components composition and custom hooks.
Top comments (0)