From 52a62c13ed8c939679cef0c249a54a592c21866d Mon Sep 17 00:00:00 2001 From: Nate Norberg Date: Wed, 4 Sep 2019 17:23:45 -0600 Subject: [PATCH 1/2] Got a hook in place --- src/CheckSome.tsx | 77 ++++++++++++++++++++++------------------------- 1 file changed, 36 insertions(+), 41 deletions(-) diff --git a/src/CheckSome.tsx b/src/CheckSome.tsx index 5ad4740..1fc14dc 100644 --- a/src/CheckSome.tsx +++ b/src/CheckSome.tsx @@ -1,4 +1,4 @@ -import React, {createContext} from 'react'; +import React, {createContext, useState} from 'react'; import isEqual from 'lodash/isEqual'; import CheckSomeField from './Field'; import {ValidationErrors} from './globals'; @@ -15,47 +15,32 @@ export type CheckSomeChildProps = { errors: ValidationGroupErrors; }; -export type CheckSomeProps = { +interface CheckSomeOptions { rules: ValidationGroupRules; values: ValidationGroupValues; initialValues?: ValidationGroupValues; +} + +export interface CheckSomeProps extends CheckSomeOptions { children: (props: CheckSomeChildProps) => React.ReactNode; -}; +} export const CheckSomeContext = createContext<{ values: {[key: string]: any}; errors: {[key: string]: ValidationErrors | undefined} | null; }>({values: {}, errors: {}}); -export default class CheckSome extends React.Component> { - static Field = CheckSomeField; - - static defaultProps = { - rules: {}, - }; - - initialValues: Object | null | undefined; +function useValidation({rules, values, initialValues}: CheckSomeOptions) { + const [compareAgainst] = useState(initialValues || values); - getInitialValues = () => { - if (this.props.initialValues) { - return this.props.initialValues; - } - - if (!this.initialValues) { - this.initialValues = this.props.values; - } - - return this.initialValues; - }; - - getErrors = (): ValidationGroupErrors => - Object.keys(this.props.rules).reduce((errors: ValidationGroupErrors, keyValue) => { + const errors: ValidationGroupErrors = Object.keys(rules).reduce( + (errors: ValidationGroupErrors, keyValue) => { // @ts-ignore const key: keyof T = keyValue; - const rules = this.props.rules[key]; - const value = this.props.values[key]; + const ruleList = rules[key]; + const value = values[key]; - const newErrors = rules!.reduce( + const newErrors = ruleList!.reduce( (e: ValidationErrors | null, rule: ValidationRule) => e || rule(value), null, ); @@ -71,20 +56,30 @@ export default class CheckSome extends React.Component> { errors[key] = newErrors; return errors; - }, null); + }, + null, + ); - render() { - const {values} = this.props; + const valid = !errors; + const changed = !isEqual(values, compareAgainst); - const errors = this.getErrors(); - const valid = !errors; + return {valid, errors, changed}; +} - const changed = !isEqual(values, this.getInitialValues()); +export const CheckSome = ({ + rules, + values, + initialValues, + children, +}: CheckSomeProps) => { + const {valid, errors, changed} = useValidation({rules, values, initialValues}); + + return ( + + {children({valid, errors, changed})} + + ); +}; +CheckSome.Field = CheckSomeField; - return ( - - {this.props.children({valid, errors, changed})} - - ); - } -} +export default CheckSome; From 1c3dedd459d6fbae4284ea9b27753109242166ad Mon Sep 17 00:00:00 2001 From: Nate Norberg Date: Wed, 25 Sep 2019 09:39:19 -0600 Subject: [PATCH 2/2] WIP --- src/CheckSome.tsx | 8 ++++-- src/index.js | 1 + test/CheckSome.test.tsx | 59 ++++++++++++++++++++++++++++++++++++++++- 3 files changed, 65 insertions(+), 3 deletions(-) diff --git a/src/CheckSome.tsx b/src/CheckSome.tsx index 1fc14dc..bd680c1 100644 --- a/src/CheckSome.tsx +++ b/src/CheckSome.tsx @@ -30,7 +30,11 @@ export const CheckSomeContext = createContext<{ errors: {[key: string]: ValidationErrors | undefined} | null; }>({values: {}, errors: {}}); -function useValidation({rules, values, initialValues}: CheckSomeOptions) { +export const useValidation = ({ + rules, + values, + initialValues, +}: CheckSomeOptions) => { const [compareAgainst] = useState(initialValues || values); const errors: ValidationGroupErrors = Object.keys(rules).reduce( @@ -64,7 +68,7 @@ function useValidation({rules, values, initialValues}: CheckSomeOptions) { const changed = !isEqual(values, compareAgainst); return {valid, errors, changed}; -} +}; export const CheckSome = ({ rules, diff --git a/src/index.js b/src/index.js index 270547c..9fb3e6e 100644 --- a/src/index.js +++ b/src/index.js @@ -1,3 +1,4 @@ import CheckSome from './CheckSome'; +export {useValidation} from './CheckSome'; export default CheckSome; diff --git a/test/CheckSome.test.tsx b/test/CheckSome.test.tsx index b89e3e4..fe95186 100644 --- a/test/CheckSome.test.tsx +++ b/test/CheckSome.test.tsx @@ -1,5 +1,5 @@ import React, {useState} from 'react'; -import CheckSome from '../src'; +import CheckSome, {useValidation} from '../src'; import {render, fireEvent} from '@testing-library/react'; const required = value => (value || value === 0 ? null : {required: {}}); @@ -24,6 +24,63 @@ const TestField = ({label, value, onValueChanged, errors, valid, touched}) => ( ); +const TestFormWithHooks = ({values, rules, initialValues = undefined}) => { + const [requiredStringValue, setRequiredStringValue] = useState(values.requiredString); + const [numberValue, setNumberValue] = useState(values.testNumber); + const [optionalStringValue, setOptionalStringValue] = useState(values.optionalString); + + const {valid, changed, errors} = useValidation({ + values: { + requiredString: requiredStringValue, + testNumber: numberValue, + optionalString: optionalStringValue, + }, + rules, + initialValues, + }); + + return ( +
+
Form {valid ? 'Valid' : 'Invalid'}
+
{changed ? 'Changed' : 'Unchanged'}
+
Form Errors: {JSON.stringify(errors)}
+ + + {fieldProps => ( + + )} + + + + {fieldProps => ( + setNumberValue(Number.parseInt(v))} + {...fieldProps} + /> + )} + + + + {fieldProps => ( + + )} + +
+ ); +}; + const TestForm = ({values, rules, initialValues = undefined}) => { const [requiredStringValue, setRequiredStringValue] = useState(values.requiredString); const [numberValue, setNumberValue] = useState(values.testNumber);