Skip to content

Instantly share code, notes, and snippets.

@gchumillas
Last active March 28, 2021 12:15
Show Gist options
  • Select an option

  • Save gchumillas/58baf61613e5f7deb243eb35c825e40a to your computer and use it in GitHub Desktop.

Select an option

Save gchumillas/58baf61613e5f7deb243eb35c825e40a to your computer and use it in GitHub Desktop.

Revisions

  1. gchumillas revised this gist Mar 28, 2021. 2 changed files with 41 additions and 51 deletions.
    67 changes: 30 additions & 37 deletions validator.ts
    Original file line number Diff line number Diff line change
    @@ -3,48 +3,41 @@ import _ from 'lodash'

    export type Validator = (val: any) => string | true

    const useValidator = (
    callback: () => {
    defaultValidator?: Validator
    validators?:
    | { [name: string]: Validator[] }
    | ((defaultValidator: Validator) => { [name: string]: Validator[] })
    },
    deps: React.DependencyList = []
    ) => {
    const useValidator = ({
    defaultValidator = () => true,
    validators
    }: {
    defaultValidator?: Validator
    validators?:
    | { [name: string]: Validator[] }
    | ((defaultValidator: Validator) => { [name: string]: Validator[] })
    }) => {
    const [texts, setTexts] = React.useState<{ [name: string]: string | true }>({})
    /* eslint-disable react-hooks/exhaustive-deps */
    const memoizedCallback = React.useCallback(callback, deps)

    return React.useMemo(
    () => ({
    test: (values: { [name: string]: any }) => {
    const res = memoizedCallback()
    const defaultValidator = res.defaultValidator || (x => true)
    const validators = res.validators
    const texts = _.mapValues(values, (value, name) => {
    const v = _.isFunction(validators) ? validators(defaultValidator) : validators
    const fieldValidators = v?.[name] || [defaultValidator]
    return {
    test: (values: { [name: string]: any }, options?: { debug: boolean }) => {
    const texts = _.mapValues(values, (value, name) => {
    const v = _.isFunction(validators) ? validators(defaultValidator) : validators
    const fieldValidators = v?.[name] || [defaultValidator]

    for (const validator of fieldValidators) {
    const result = validator(value)
    if (typeof result == 'string') {
    return result
    }
    for (const validator of fieldValidators) {
    const result = validator(value)
    if (typeof result == 'string') {
    options?.debug && console.info(`validator: ${name} is not valid`)
    return result
    }
    }

    return true
    })
    setTexts(texts)
    return _.every(texts, x => _.isEmpty(x))
    },
    text: (name: string) => {
    const text = texts[name]
    return typeof text == 'string' ? text : undefined
    }
    }),
    [memoizedCallback, texts]
    )
    return true
    })
    setTexts(texts)
    return _.every(texts, x => _.isEmpty(x))
    },
    text: (name: string) => {
    const text = texts[name]
    return typeof text == 'string' ? text : undefined
    }
    }
    }

    export default useValidator
    25 changes: 11 additions & 14 deletions validator_example.jsx
    Original file line number Diff line number Diff line change
    @@ -15,20 +15,17 @@ const TestPage = () => {
    gender: ''
    })

    const validator = useValidator(
    () => ({
    // Default validator (applied when no validator is specified)
    defaultValidator: val => !!val || 'Required field',
    // Custom validators
    validators: defaultValidator => ({
    // rePassword is required (defaultValidator) and should be equal to password
    rePassword: [defaultValidator, val => val == item.password || 'Passwords do not match'],
    // Gender is not required, but when specified it must be one of the following values:
    gender: [val => !val || ['male', 'female', 'butterfly'].includes(val) || 'Unknown gender']
    })
    }),
    [item.password]
    )
    const validator = useValidator({
    // Default validator (applied when no validator is specified)
    defaultValidator: val => !!val || 'Required field',
    // Custom validators
    validators: defaultValidator => ({
    // rePassword is required (defaultValidator) and should be equal to password
    rePassword: [defaultValidator, val => val == item.password || 'Passwords do not match'],
    // Gender is not required, but when specified it must be one of the following values:
    gender: [val => !val || ['male', 'female', 'butterfly'].includes(val) || 'Unknown gender']
    })
    })

    const onFieldChange = name => event => {
    setItem(item => ({ ...item, [name]: event.target.value }))
  2. gchumillas revised this gist Mar 27, 2021. 1 changed file with 5 additions and 0 deletions.
    5 changes: 5 additions & 0 deletions validator_example.jsx
    Original file line number Diff line number Diff line change
    @@ -7,6 +7,7 @@ const CustomTextField = ({ errorText, ...rest }) => {
    }

    const TestPage = () => {
    const [message, setMessage] = React.useState('')
    const [item, setItem] = React.useState({
    username: '',
    password: '',
    @@ -35,8 +36,11 @@ const TestPage = () => {

    const onSubmit = () => {
    if (!validator.test(item)) {
    setMessage('No no no...')
    return
    }

    setMessage('Hooray! you got it!')
    }

    return (
    @@ -71,6 +75,7 @@ const TestPage = () => {
    errorText={validator.text('gender')}
    />
    <Button onClick={onSubmit}>Submit</Button>
    <p>{message}</p>
    </>
    )
    }
  3. gchumillas renamed this gist Mar 27, 2021. 1 changed file with 0 additions and 0 deletions.
    File renamed without changes.
  4. gchumillas created this gist Mar 27, 2021.
    78 changes: 78 additions & 0 deletions example.jsx
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,78 @@
    import React from 'react'
    import { Button, TextField } from '@material-ui/core'
    import useValidator from 'src/lib/validator'

    const CustomTextField = ({ errorText, ...rest }) => {
    return <TextField error={!!errorText} helperText={errorText} {...rest} />
    }

    const TestPage = () => {
    const [item, setItem] = React.useState({
    username: '',
    password: '',
    rePassword: '',
    gender: ''
    })

    const validator = useValidator(
    () => ({
    // Default validator (applied when no validator is specified)
    defaultValidator: val => !!val || 'Required field',
    // Custom validators
    validators: defaultValidator => ({
    // rePassword is required (defaultValidator) and should be equal to password
    rePassword: [defaultValidator, val => val == item.password || 'Passwords do not match'],
    // Gender is not required, but when specified it must be one of the following values:
    gender: [val => !val || ['male', 'female', 'butterfly'].includes(val) || 'Unknown gender']
    })
    }),
    [item.password]
    )

    const onFieldChange = name => event => {
    setItem(item => ({ ...item, [name]: event.target.value }))
    }

    const onSubmit = () => {
    if (!validator.test(item)) {
    return
    }
    }

    return (
    <>
    <CustomTextField
    required
    label="Username"
    value={item.username}
    onChange={onFieldChange('username')}
    errorText={validator.text('username')}
    />
    <CustomTextField
    required
    type="password"
    label="Password"
    value={item.password}
    onChange={onFieldChange('password')}
    errorText={validator.text('password')}
    />
    <CustomTextField
    required
    type="password"
    label="Repeat password"
    value={item.rePassword}
    onChange={onFieldChange('rePassword')}
    errorText={validator.text('rePassword')}
    />
    <CustomTextField
    label="Gender"
    value={item.gender}
    onChange={onFieldChange('gender')}
    errorText={validator.text('gender')}
    />
    <Button onClick={onSubmit}>Submit</Button>
    </>
    )
    }

    export default TestPage
    50 changes: 50 additions & 0 deletions validator.ts
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,50 @@
    import React from 'react'
    import _ from 'lodash'

    export type Validator = (val: any) => string | true

    const useValidator = (
    callback: () => {
    defaultValidator?: Validator
    validators?:
    | { [name: string]: Validator[] }
    | ((defaultValidator: Validator) => { [name: string]: Validator[] })
    },
    deps: React.DependencyList = []
    ) => {
    const [texts, setTexts] = React.useState<{ [name: string]: string | true }>({})
    /* eslint-disable react-hooks/exhaustive-deps */
    const memoizedCallback = React.useCallback(callback, deps)

    return React.useMemo(
    () => ({
    test: (values: { [name: string]: any }) => {
    const res = memoizedCallback()
    const defaultValidator = res.defaultValidator || (x => true)
    const validators = res.validators
    const texts = _.mapValues(values, (value, name) => {
    const v = _.isFunction(validators) ? validators(defaultValidator) : validators
    const fieldValidators = v?.[name] || [defaultValidator]

    for (const validator of fieldValidators) {
    const result = validator(value)
    if (typeof result == 'string') {
    return result
    }
    }

    return true
    })
    setTexts(texts)
    return _.every(texts, x => _.isEmpty(x))
    },
    text: (name: string) => {
    const text = texts[name]
    return typeof text == 'string' ? text : undefined
    }
    }),
    [memoizedCallback, texts]
    )
    }

    export default useValidator