Skip to content

Instantly share code, notes, and snippets.

@mvanlonden
Last active October 3, 2017 04:32
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mvanlonden/a592f5c30be487d53ff73c30a6cfe724 to your computer and use it in GitHub Desktop.
Save mvanlonden/a592f5c30be487d53ff73c30a6cfe724 to your computer and use it in GitHub Desktop.
React HOC for form submission
import { isArray, keys, isEmpty, values } from 'lodash'
import { compose, curry, map, mapValues, mergeWith, get, pickBy, set, pick, every } from 'lodash/fp'
import { withState, mapProps } from 'recompose'
const makePredicate = ([test, errorMsg]) => a =>
test(a) ? null : errorMsg
const makePredicates = map(makePredicate)
const runPredicates = ({ input, validations }) =>
map(predFn => predFn(input), makePredicates(validations))
const validate = mapValues(runPredicates)
const makeValidationObject = mergeWith((input, validations) =>
({ input, validations }))
const filterWithValidation = pickBy(compose(isArray, get('validations')))
const errorMessage = result =>
result.filter(error => error !== null).join(', ')
const getErrors = compose(
mapValues(errorMessage),
validate,
filterWithValidation,
makeValidationObject
)
const checkCanSubmit = compose(
every(isEmpty),
values
)
export default (validationRules, formStateSetter) => Component => compose(
withState('isSubmitting', 'setIsSubmitting', false),
withState('state', 'updateState', props => ({
form: formStateSetter(props),
errors: {},
touched: {}
})),
withState('canSubmit', 'setCanSubmit', ({ state }) => compose(
checkCanSubmit,
getErrors
)(get('form', state), validationRules)),
mapProps(({ updateState, state, setCanSubmit, ...rest }) => ({
onChange: curry((name, value) =>
updateState(state => {
const newState = compose(
set(['form', name], value),
set(['touched', name], true)
)(state)
const touched = compose(
keys,
pickBy(Boolean)
)(get('touched', state))
const errors = getErrors(get('form', newState), validationRules)
setCanSubmit(checkCanSubmit(errors))
const visibleErrors = pick(touched, errors)
return set('errors', visibleErrors, newState)
})
),
form: get('form', state),
errors: get('errors', state),
...rest
}))
)(Component)
import withForm from './withForm'
// helper
const getValue = R.path(['target', 'value'])
// validations
const isNotEmpty = a => a.trim().length > 0
const hasCapitalLetter = a => /[A-Z]/.test(a)
const isGreaterThan = R.curry((len, a) => (a > len))
const isLengthGreaterThan = len => R.compose(isGreaterThan(len), R.prop('length'))
const StatelessFunction = ({ form, onChange, onSubmit, errors, canSubmit, isSubmitting, setIsSubmitting }) =>
<form
onSubmit={e =>
e.preventDefault()
if(!canSubmit) {
return
}
setIsSubmitting(true)
doThingForFormSubmission(form)
.then(() =>
setIsSubmitting(false)
)
.catch(error => {
setIsSubmitting(false)
console.warn(error)
})
}
>
<div className='formGroup'>
<label>Name</label>
<input
type='text'
value={form.name}
onChange={R.compose(onChange('name'), getValue)}
/>
{ errors.name }
</div>
<div className='formGroup'>
<label>Random</label>
<input
type='text'
value={form.random}
onChange={R.compose(onChange('random'), getValue)}
/>
{ errors.random }
</div>
<button type='submit' disabled={!canSubmit || isSubmitting}>Submit</button>
<form>
const validationRules = {
name: [
[isNotEmpty, 'Name should not be empty.']
],
random: [
[ isLengthGreaterThan(7), 'Minimum Random length of 8 is required.' ],
[ hasCapitalLetter, 'Random should contain at least one uppercase letter.' ],
]
}
const initialFormState = {name: '', random: ''}
const enhanced = withForm(validationRules, initialFormState)
export default enhanced(StatelessFunction)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment