import React from 'react';
import { useValidation, useFormFeedback } from './use-validation';
type InputItemProps = {
name,
value
} & FieldProps<string>
const InputItem: FC.React<FieldProps> = ({
name,
validation,
value,
showError,
onChange
}) => {
const {
onChange: handleInputChange,
onBlur,
// TODO: use.
shouldShowError,
errorMessages
} = useFormFeedback<string>({ validation, value, showError });
const labelStyle = {};
const inputStyle = {};
if (shouldShowError) {
Object.assign(labelStyle, { color: 'red' });
Object.assign(inputStyle, { borderColor: 'red' });
}
return (
<div>
<label style={labelStyle} for={name}>First Name</label>
<input
style={inputStyle}
id={name}
name={name}
onChange={e => {
onChange(e.target.value);
handleInputChange();
}} />
</div>
)
};
const required = (name: string) =>
requiredCustomMessage(`${name} is required`);
const requiredCustomMessage = (message: string) =>
(value: string) => (value).trim().length > 0 ? null : [ message ];
function useFormValidation() {
const config = {
firstName: { validation: required('First name') },
lastName: { validation: required('Last name') },
email: { validation: required('Email') },
};
const initialState = {
firstName: { value: '' },
lastName: { value: '' },
email: { value: '' }
};
return useValidation(config, initialState);
}
function Form() {
const { getFormProps, validate } = useValidation()
return (
<form onSubmit={e => {
if (!validate()) {
e.preventDefault();
}
}}>
<InputItem name='first_name' {...getFormProps('firstName')} />
<InputItem name='last_name' {...getFormProps('lastName')} />
<InputItem name='email' {...getFormProps('email')} />
<input type='submit' name='submit' value='Submit' />
</form>
)
}
Created
August 21, 2019 01:41
-
-
Save shovon/4ac7487b2346684d8831155bd7b53330 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { useReducer, useState } from 'react'; | |
/** | |
* The error of a single field. | |
*/ | |
export type FieldError = string[] | null | |
/** | |
* The function that serves to validate a value. | |
*/ | |
type ValidationFunction<T> = (value: T) => FieldError | |
/** | |
* | |
*/ | |
type ValidationFunctionWithState<T, K extends T[keyof T]> = (value: K, state: State<T>) => FieldError | |
/** | |
* The configuration object used to represent a single field in a form. | |
*/ | |
type ConfigField<T, P extends T[keyof T]> = { | |
validation: ValidationFunctionWithState<T, P> | |
} | |
/** | |
* The configuration object for the form validation. | |
*/ | |
export type Config<T> = { | |
fields: { | |
[P in keyof T]: ConfigField<T, T[P]> | |
} | |
} | |
/** | |
* The field within a single state. | |
*/ | |
type StateField<T> = { | |
value: T | |
} | |
// Not sure if this is even necessary. | |
export type State<T> = { | |
forceShowErrors: boolean | |
fields: { | |
[P in keyof T]: StateField<T[P]> | |
} | |
} | |
/** | |
* The field's props. | |
*/ | |
export type FieldProps<T> = { | |
value: T | |
validation: ValidationFunction<T> | |
onChange: (v: T) => void | |
showError: boolean | |
} | |
/** | |
* The object returned by the `useValidation` hook. | |
*/ | |
type ValidationHook<T> = { | |
/** | |
* Gets the fields' props. | |
* @param key The string key to get the form from. | |
*/ | |
getFieldProps<K extends keyof T>(key: K): FieldProps<T[K]> | |
/** | |
* Triggers a validation. | |
*/ | |
validate(): void | |
} | |
/** | |
* The payload inside the change action. | |
*/ | |
type ChangeActionPayload<T, K extends keyof T> = { | |
key: K | |
value: T[K] | |
} | |
/** | |
* The change action. | |
*/ | |
type ChangeAction<T> = { | |
type: 'CHANGE' | |
payload: ChangeActionPayload<T, keyof T> | |
} | |
/** | |
* The action to force the showing of errors. | |
*/ | |
type ForceShowError = { | |
type: 'FORCE_SHOW_ERROR' | |
value: boolean | |
} | |
/** | |
* All the possible actions that we can take, on a form. | |
*/ | |
type FormStateActions<T> = ChangeAction<T> | ForceShowError | |
/** | |
* The React reducer function for the form state. | |
* @param state The current state | |
* @param action The action to dispatch. | |
*/ | |
function formStateReducer<T>(state: State<T>, action: FormStateActions<T>): State<T> { | |
switch (action.type) { | |
case 'CHANGE': { | |
const { payload } = action; | |
return { ...state, fields: { ...state.fields, [payload.key]: { value: payload.value } } } | |
} | |
case 'FORCE_SHOW_ERROR': { | |
return { ...state, forceShowErrors: true } | |
} | |
} | |
console.log(state); | |
return state; | |
} | |
/** | |
* The function type to represent the form's state reducer. | |
*/ | |
type FormStateReducer<T> = React.Reducer<State<T>, FormStateActions<T>>; | |
/** | |
* Creates the form validation hook. | |
* @param config The form validation configuration | |
* @param initialState The initial state | |
*/ | |
export function useValidation<T>(config: Config<T>, initialState: State<T>): ValidationHook<T> { | |
const [ state, dispatch ] = useReducer<FormStateReducer<T>>(formStateReducer, initialState); | |
let subsequentState = { ...state }; | |
return { | |
getFieldProps: key => { | |
return ({ | |
showError: subsequentState.forceShowErrors, | |
value: subsequentState.fields[key].value, | |
validation: value => { | |
return config.fields[key].validation(value, subsequentState); | |
}, | |
onChange: value => { | |
dispatch({ type: 'CHANGE', payload: { key, value } }) | |
} | |
}) | |
}, | |
validate: () => { | |
throw new Error('Not yet implemented'); | |
} | |
} | |
} | |
/** | |
* The props for the form feedback hook. | |
*/ | |
export type FormFeedbackProps<T> = { | |
validation: ValidationFunction<T> | |
value: T | |
showError: boolean | |
} | |
/** | |
* A hook for a form's feedback information. | |
* @param config The props needed for the form feedback. | |
*/ | |
export function useFormFeedback<T>({ validation, value, showError }: FormFeedbackProps<T>) { | |
const [ didBlur, setDidBlur ] = useState({ value: false }); | |
const [ didChange, setDidChange ] = useState(false); | |
const errorMessages = validation(value); | |
const shouldShowError = ( | |
(didBlur.value && didChange) || showError) && | |
(errorMessages !== null && errorMessages.length > 0 | |
); | |
return { | |
onChange: () => { | |
setDidChange(true); | |
}, | |
onBlur: () => { | |
setDidBlur({ value: true }); | |
}, | |
shouldShowError, | |
errorMessages | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment