Skip to content

Instantly share code, notes, and snippets.

@wokalski
Last active August 23, 2020 05:58
Show Gist options
  • Save wokalski/e6702ebae3dffb93de676252bdbdacc8 to your computer and use it in GitHub Desktop.
Save wokalski/e6702ebae3dffb93de676252bdbdacc8 to your computer and use it in GitHub Desktop.
sensible forms for reason-react
type validatorData('a) = {
value: 'a,
isTouched: bool,
submitted: bool,
};
type shouldDisplayError('error) = {
error: 'error,
isTouched: bool,
submitted: bool,
};
type field('a, 'action, 'error) = {
value: unit => 'a,
validate: unit => result('a, 'error),
validateValueOnly: unit => result('a, 'error),
onChange: 'action => unit,
};
type form = {
mutable submit: unit => unit,
mutable isValid: unit => bool,
};
let addField = (form, handler, validator) => {
let submit = form.submit;
let isValid = form.isValid;
form.submit = (
() => {
submit();
handler();
}
);
form.isValid = (
() => {
isValid() && validator();
}
);
};
let useForm = () => {
{submit: () => (), isValid: () => true};
};
let useFieldWithReducer =
(
~form,
~initialValue,
validator,
~displayError=({error: _, isTouched, submitted}) => {
!isTouched && submitted
},
reducer,
) => {
let (_, setValue) = React.useState(() => initialValue);
let valueRef = React.useRef(initialValue);
let (touched, setTouched) = React.useState(() => false);
let (submitted, setSubmitted) = React.useState(() => false);
let onSubmit = () => {
setTouched(_ => false);
setSubmitted(_ => true);
};
let validateValueOnly = () => {
validator({
value: React.Ref.current(valueRef),
isTouched: touched,
submitted,
});
};
addField(form, onSubmit, () => Belt.Result.isOk(validateValueOnly()));
let value = () => React.Ref.current(valueRef);
let validate = () => {
switch (validateValueOnly()) {
| Error(error) =>
if (displayError({
error,
isTouched: touched,
submitted,
})) {
Error(error);
} else {
Ok(value());
}
| Ok(value) => Ok(value)
};
};
{
value,
validate,
validateValueOnly,
onChange: action => {
let currentValue = React.Ref.current(valueRef);
let nextValue = reducer(action, currentValue);
React.Ref.setCurrent(valueRef, nextValue);
setTouched(_ => false)
setValue(_ => nextValue);
},
};
};
let useField = (~form, ~initialValue, ~displayError=?, validator) => {
useFieldWithReducer(
~form, ~initialValue, validator, ~displayError?, (newValue, _) =>
newValue
);
};
let onSubmit = ({submit}) => submit();
let isValid = ({isValid}) => isValid();
type form;
type validatorData('a) = {
value: 'a,
isTouched: bool,
submitted: bool,
};
type shouldDisplayError('error) = {
error: 'error,
isTouched: bool,
submitted: bool,
};
type field('a, 'action, 'error) = {
value: unit => 'a,
validate: unit => result('a, 'error),
validateValueOnly: unit => result('a, 'error),
onChange: 'action => unit,
};
let useForm: unit => form;
let useFieldWithReducer:
(
~form: form,
~initialValue: 'state,
validatorData('state) => result('state, 'error),
~displayError: shouldDisplayError('error) => bool=?,
('action, 'state) => 'state
) =>
field('state, 'action, 'error);
let useField:
(
~form: form,
~initialValue: 'state,
~displayError: shouldDisplayError('error) => bool=?,
validatorData('state) => result('state, 'error),
) =>
field('state, 'state, 'error);
let onSubmit: form => unit;
let isValid: form => bool;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment